From ce5f8a87bb5d411bae67c39b269d879677bb98da Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期日, 08 十一月 2026 19:29:25 +0800
Subject: [PATCH] feat(hrm): 请假导入、加班代申请、批量排班与考勤应出勤重算

---
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmOvertimeApplicationServiceImpl.java |   90 ++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 76 insertions(+), 14 deletions(-)

diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmOvertimeApplicationServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmOvertimeApplicationServiceImpl.java
index a044fba..73d4550 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmOvertimeApplicationServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmOvertimeApplicationServiceImpl.java
@@ -4,8 +4,10 @@
 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;
@@ -56,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());
@@ -73,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));
+        // 鏈噸鏂伴�夋嫨鐢宠浜烘椂锛屾部鐢ㄥ師鐢宠浜猴紝閬垮厤浠g敵璇峰崟琚敼鍥炵櫥褰曚汉
+        Long requestedEmployeeId = reqVO.getEmployeeId() != null ? reqVO.getEmployeeId() : existing.getEmployeeId();
+        applyApplicantAndCalculatedTime(update, resolveApplicant(requestedEmployeeId, loginUserId));
         update.setId(existing.getId());
         mapper.updateById(update);
     }
@@ -83,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);
@@ -117,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,
+        // 浠ョ敵璇蜂汉鍛樺伐瀵瑰簲鐨勭郴缁熻处鍙峰彂璧锋祦绋嬶紝淇濊瘉浠g敵璇峰崟鐨勫鎵逛汉鑳界湅鍒版纭殑鐢宠浜�
+        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("鐢宠浜烘挙閿�鍔犵彮鐢宠"));
     }
 
@@ -165,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() {
@@ -217,13 +245,47 @@
 
     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;
     }
 
+    /**
+     * 瑙f瀽鏈鐢宠瀵瑰簲鐨勫憳宸ユ。妗堛��
+     *
+     * @param requestedEmployeeId 鍓嶇閫夋嫨鐨勭敵璇蜂汉鍛樺伐ID锛涗负绌鸿〃绀烘湰浜�
+     * @param loginUserId         褰撳墠鐧诲綍鐢ㄦ埛ID
+     * @return 鐢宠浜哄搴旂殑鍛樺伐妗f
+     */
+    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;
+        }
+        // 浠g敵璇凤細蹇呴』鏈夌鐞嗘潈闄�
+        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) {
         HrmEmployeeDO employee = employeeMapper.selectByUserId(userId);
         if (employee == null) throw exception(OVERTIME_APPLICATION_EMPLOYEE_NOT_EXISTS);

--
Gitblit v1.9.3