gaoluyang
5 天以前 ce5f8a87bb5d411bae67c39b269d879677bb98da
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmOvertimeApplicationServiceImpl.java
@@ -2,8 +2,12 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService;
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;
@@ -54,11 +58,12 @@
    @Resource private BpmTaskService bpmTaskService;
    @Resource private BpmCategoryService bpmCategoryService;
    @Resource private BpmProcessDefinitionService bpmProcessDefinitionService;
    @Resource private SecurityFrameworkService securityFrameworkService;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long create(HrmOvertimeApplicationSaveReqVO reqVO, Long loginUserId) {
        HrmEmployeeDO employee = requireEmployee(loginUserId);
        HrmEmployeeDO employee = resolveApplicant(reqVO.getEmployeeId(), loginUserId);
        HrmOvertimeApplicationDO application = BeanUtils.toBean(reqVO, HrmOvertimeApplicationDO.class);
        applyApplicantAndCalculatedTime(application, employee);
        application.setNo(noRedisDAO.generateOvertimeApplicationNo());
@@ -71,7 +76,9 @@
    public void update(HrmOvertimeApplicationSaveReqVO reqVO, Long loginUserId) {
        HrmOvertimeApplicationDO existing = requireOwnedDraft(reqVO.getId(), loginUserId);
        HrmOvertimeApplicationDO update = BeanUtils.toBean(reqVO, HrmOvertimeApplicationDO.class);
        applyApplicantAndCalculatedTime(update, requireEmployee(loginUserId));
        // 未重新选择申请人时,沿用原申请人,避免代申请单被改回登录人
        Long requestedEmployeeId = reqVO.getEmployeeId() != null ? reqVO.getEmployeeId() : existing.getEmployeeId();
        applyApplicantAndCalculatedTime(update, resolveApplicant(requestedEmployeeId, loginUserId));
        update.setId(existing.getId());
        mapper.updateById(update);
    }
@@ -81,7 +88,10 @@
        HrmOvertimeApplicationDO application = require(id);
        boolean deletable = Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())
                || Objects.equals(application.getStatus(), HrmAuditStatusEnum.CANCEL.getStatus());
        if (!Objects.equals(application.getUserId(), loginUserId) || !deletable) {
        if (!Objects.equals(application.getUserId(), loginUserId) && !canManageOvertime()) {
            throw exception(OVERTIME_APPLICATION_OPERATOR_NOT_ALLOWED);
        }
        if (!deletable) {
            throw exception(OVERTIME_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
        }
        mapper.deleteById(id);
@@ -115,27 +125,43 @@
    @Transactional(rollbackFor = Exception.class)
    public void submit(Long id, String processDefinitionKey, Long loginUserId) {
        HrmOvertimeApplicationDO application = requireOwnedDraft(id, loginUserId);
        // 全局免审批:业务校验通过后直接执行审批通过逻辑,无需流程模型。
        if (bpmProcessInstanceApi.isGlobalAutoApprove()) {
            mapper.updateById(HrmOvertimeApplicationDO.builder().id(id).processInstanceId("")
                    .status(HrmAuditStatusEnum.PROCESS.getStatus()).build());
            updateAuditStatus(id, HrmAuditStatusEnum.APPROVE.getStatus());
            return;
        }
        validateProcessDefinitions();
        ProcessDefinition definition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
        if (definition == null || !BPM_PROCESS_DEFINITION_KEY.equals(definition.getKey())) {
            throw exception(OVERTIME_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(loginUserId,
        // 以申请人员工对应的系统账号发起流程,保证代申请单的审批人能看到正确的申请人
        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(application.getUserId(),
                new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(processDefinitionKey)
                        .setBusinessKey(String.valueOf(id)));
        mapper.updateById(HrmOvertimeApplicationDO.builder().id(id).processInstanceId(processInstanceId)
                .status(HrmAuditStatusEnum.PROCESS.getStatus()).build());
                        .setBusinessKey(String.valueOf(id))
                        .setVariables(Map.of("businessNo", application.getNo())));
        mapper.update(null, new LambdaUpdateWrapper<HrmOvertimeApplicationDO>()
                .eq(HrmOvertimeApplicationDO::getId, id)
                .set(HrmOvertimeApplicationDO::getProcessInstanceId, processInstanceId)
                .set(HrmOvertimeApplicationDO::getStatus, HrmAuditStatusEnum.PROCESS.getStatus())
                .set(HrmOvertimeApplicationDO::getApproveTime, null));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancel(Long id, Long loginUserId) {
        HrmOvertimeApplicationDO application = require(id);
        if (!Objects.equals(application.getUserId(), loginUserId)
                || !Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
        if (!Objects.equals(application.getUserId(), loginUserId) && !canManageOvertime()) {
            throw exception(OVERTIME_APPLICATION_OPERATOR_NOT_ALLOWED);
        }
        if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
            throw exception(OVERTIME_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
        }
        bpmProcessInstanceService.cancelProcessInstanceByStartUser(loginUserId,
        // 流程由申请人员工发起,撤销也必须以该员工账号作为发起人
        bpmProcessInstanceService.cancelProcessInstanceByStartUser(application.getUserId(),
                new BpmProcessInstanceCancelReqVO().setId(application.getProcessInstanceId()).setReason("申请人撤销加班申请"));
    }
@@ -163,8 +189,12 @@
        if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
            throw exception(OVERTIME_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
        }
        mapper.updateById(HrmOvertimeApplicationDO.builder().id(id).status(status)
                .approveTime(LocalDateTime.now()).build());
        mapper.update(null, new LambdaUpdateWrapper<HrmOvertimeApplicationDO>()
                .eq(HrmOvertimeApplicationDO::getId, id)
                .set(HrmOvertimeApplicationDO::getStatus, status)
                .set(HrmOvertimeApplicationDO::getApproveTime,
                        Objects.equals(status, HrmAuditStatusEnum.APPROVE.getStatus())
                                ? LocalDateTime.now() : null));
    }
    private void validateProcessDefinitions() {
@@ -215,11 +245,45 @@
    private HrmOvertimeApplicationDO requireOwnedDraft(Long id, Long userId) {
        HrmOvertimeApplicationDO value = require(id);
        if (!Objects.equals(value.getUserId(), userId)
                || !Objects.equals(value.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
        // 持有 hrm:overtime:manage 权限可操作全部人的单子;否则只能操作本人单子
        if (!Objects.equals(value.getUserId(), userId) && !canManageOvertime()) {
            throw exception(OVERTIME_APPLICATION_OPERATOR_NOT_ALLOWED);
        }
        if (!Objects.equals(value.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())
                && !Objects.equals(value.getStatus(), HrmAuditStatusEnum.REJECT.getStatus())) {
            throw exception(OVERTIME_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
        }
        return value;
    }
    /**
     * 解析本次申请对应的员工档案。
     *
     * @param requestedEmployeeId 前端选择的申请人员工ID;为空表示本人
     * @param loginUserId         当前登录用户ID
     * @return 申请人对应的员工档案
     */
    private HrmEmployeeDO resolveApplicant(Long requestedEmployeeId, Long loginUserId) {
        HrmEmployeeDO self = employeeMapper.selectByUserId(loginUserId);
        if (requestedEmployeeId == null || (self != null && Objects.equals(self.getId(), requestedEmployeeId))) {
            if (self == null) {
                throw exception(OVERTIME_APPLICATION_EMPLOYEE_NOT_EXISTS);
            }
            return self;
        }
        // 代申请:必须有管理权限
        if (!canManageOvertime()) {
            throw exception(OVERTIME_APPLICATION_PROXY_NOT_ALLOWED);
        }
        HrmEmployeeDO target = employeeMapper.selectById(requestedEmployeeId);
        if (target == null) {
            throw exception(OVERTIME_APPLICATION_SELECTED_EMPLOYEE_NOT_EXISTS);
        }
        return target;
    }
    private boolean canManageOvertime() {
        return securityFrameworkService.hasPermission("hrm:overtime:manage");
    }
    private HrmEmployeeDO requireEmployee(Long userId) {
@@ -255,9 +319,17 @@
        resp.setEmployeeName(employee.getName()); resp.setEmployeeNo(employee.getEmployeeNo());
        resp.setDeptId(employee.getDeptId());
        DeptRespDTO dept = employee.getDeptId() == null ? null : deptApi.getDept(employee.getDeptId());
        PostRespDTO post = employee.getPostId() == null ? null
                : postApi.getPostMap(Collections.singleton(employee.getPostId())).get(employee.getPostId());
        resp.setDeptName(dept == null ? null : dept.getName());
        resp.setPostName(post == null ? null : post.getName());
        // 岗位多选:解析 postIds JSON,拼接岗位名称
        if (StrUtil.isNotBlank(employee.getPostIds())) {
            List<Long> postIds = JSONUtil.toList(employee.getPostIds(), Long.class);
            if (CollUtil.isNotEmpty(postIds)) {
                Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds);
                resp.setPostName(postIds.stream()
                        .filter(postMap::containsKey)
                        .map(id -> postMap.get(id).getName())
                        .collect(Collectors.joining(",")));
            }
        }
    }
}