From 82155dd6f2b16fbfb718e636a54e2c9dae31246b Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期五, 30 十月 2026 20:23:20 +0800
Subject: [PATCH] feat(hrm,bi): 移植长辉电力的考勤请假联动与 BI 大屏图表

---
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryServiceImpl.java |  128 +++++++++++++++++++++++++++++++++++++-----
 1 files changed, 113 insertions(+), 15 deletions(-)

diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryServiceImpl.java
index 67eb1ef..9e05c94 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryServiceImpl.java
@@ -5,11 +5,13 @@
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceSummaryPageReqVO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRuleDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceSummaryDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
 import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceRecordMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceSummaryMapper;
 import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceClockTypeEnum;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
 import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
@@ -19,6 +21,7 @@
 
 import java.math.BigDecimal;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.YearMonth;
 import java.util.List;
 
@@ -40,6 +43,10 @@
     private HrmEmployeeService employeeService;
     @Resource
     private HrmAttendanceHolidayService attendanceHolidayService;
+    @Resource
+    private cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmLeaveApplicationMapper leaveApplicationMapper;
+    @Resource
+    private HrmAttendanceRuleService attendanceRuleService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -59,6 +66,14 @@
         // 3. 鏌ヨ璇ユ湀鎵�鏈夎�冨嫟璁板綍
         List<HrmAttendanceRecordDO> records = attendanceRecordMapper.selectListByUserIdAndDateBetween(userId, startDate, endDate);
 
+        // 3.1 鏌ヨ璇ユ湀鎵�鏈夊鎵归�氳繃鐨勮鍋囧崟
+        //     鑰冨嫟璁板綍鍙湪銆屾墦鍗°�嶆椂鎵嶅垱寤猴紝璇峰亣褰撳ぉ娌℃潵涓婄彮灏变笉浼氭湁璁板綍锛�
+        //     鎵�浠ュ繀椤诲崟鐙煡璇峰亣鍗曪紝鍚﹀垯 leaveHours 浼氭紡绠椼��
+        List<cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO> monthLeaves =
+                leaveApplicationMapper.selectListByUserIdAndOverlapDate(
+                        userId, HrmAuditStatusEnum.APPROVE.getStatus(),
+                        startDate.atStartOfDay(), endDate.plusDays(1).atStartOfDay());
+
         // 4. 璁$畻搴斿嚭鍕ゅぉ鏁帮紙宸ヤ綔鏃ユ暟閲忥級
         int workDays = calculateWorkDays(startDate, endDate);
 
@@ -67,31 +82,76 @@
         int earlyCount = 0;
         int absentCount = 0;
         BigDecimal overtimeHours = BigDecimal.ZERO;
+        BigDecimal leaveHours = BigDecimal.ZERO;
         int actualDays = 0;
 
         for (HrmAttendanceRecordDO record : records) {
-            // 缁熻杩熷埌
-            if (HrmAttendanceClockTypeEnum.LATE.getType().equals(record.getClockInType())) {
-                lateCount++;
+            // 璇峰亣鏍囪锛氬鎵归�氳繃鐨勮鍋囧崟钀藉簱鏃跺凡鍐欏叆 record.leaveFlag
+            boolean onLeave = Boolean.TRUE.equals(record.getLeaveFlag());
+            // 绱璇峰亣宸ユ椂
+            if (onLeave && record.getLeaveHours() != null) {
+                leaveHours = leaveHours.add(record.getLeaveHours());
             }
-            // 缁熻鏃╅��
-            if (HrmAttendanceClockTypeEnum.EARLY.getType().equals(record.getClockOutType())) {
-                earlyCount++;
-            }
-            // 缁熻缂哄崱
-            if (HrmAttendanceClockTypeEnum.MISSING.getType().equals(record.getClockInType())) {
-                absentCount++;
-            }
-            if (HrmAttendanceClockTypeEnum.MISSING.getType().equals(record.getClockOutType())) {
-                absentCount++;
+            // 璇峰亣褰撳ぉ涓嶈杩熷埌銆佹棭閫�銆佺己鍗★紙璇峰亣绫诲瀷鏈韩鏄� LEAVE锛屼笉浼氬懡涓笅闈㈢殑鍒ゆ柇锛�
+            if (!onLeave) {
+                // 缁熻杩熷埌
+                if (HrmAttendanceClockTypeEnum.LATE.getType().equals(record.getClockInType())) {
+                    lateCount++;
+                }
+                // 缁熻鏃╅��
+                if (HrmAttendanceClockTypeEnum.EARLY.getType().equals(record.getClockOutType())) {
+                    earlyCount++;
+                }
+                // 缁熻缂哄崱锛氳鍋囧綋澶╂病鎵撳崱灞炴甯革紝涓嶇畻缂哄崱
+                if (HrmAttendanceClockTypeEnum.MISSING.getType().equals(record.getClockInType())) {
+                    absentCount++;
+                }
+                if (HrmAttendanceClockTypeEnum.MISSING.getType().equals(record.getClockOutType())) {
+                    absentCount++;
+                }
             }
             // 绱鍔犵彮鏃堕暱
             if (record.getOvertimeHours() != null) {
                 overtimeHours = overtimeHours.add(record.getOvertimeHours());
             }
-            // 缁熻瀹為檯鍑哄嫟澶╂暟锛堟湁姝e父鎵撳崱璁板綍锛�
+            // 缁熻瀹為檯鍑哄嫟澶╂暟锛堟湁鎵撳崱璁板綍鍗崇畻锛涚函璇峰亣鏈墦鍗′笉绠楀嚭鍕わ級
             if (record.getClockInTime() != null || record.getClockOutTime() != null) {
                 actualDays++;
+            }
+        }
+
+        // 5.1 琛ョ畻銆屾湁璇峰亣鍗曚絾娌℃湁鑰冨嫟璁板綍銆嶇殑鏃ュ瓙
+        //     杩欑被鎯呭喌鏄鍋囧綋澶╁畬鍏ㄦ病鏉ユ墦鍗★紝涓嶄細鐢熸垚 record锛�
+        //     涓婇潰寰幆瑕嗙洊涓嶅埌锛岄渶瑕佸崟鐙寜璇峰亣鍗曠疮璁″伐鏃躲��
+        if (!monthLeaves.isEmpty()) {
+            java.util.Set<LocalDate> recordDates = records.stream()
+                    .map(HrmAttendanceRecordDO::getDate).collect(java.util.stream.Collectors.toSet());
+            HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId());
+            // 鎸夊ぉ褰掔粍璇峰亣鍗�
+            java.util.Map<LocalDate, List<cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO>> byDate
+                    = new java.util.LinkedHashMap<>();
+            for (var leave : monthLeaves) {
+                if (leave.getStartTime() == null || leave.getEndTime() == null) {
+                    continue;
+                }
+                LocalDate d = leave.getStartTime().toLocalDate();
+                LocalDate last = leave.getEndTime().toLocalDate();
+                if (last.isAfter(endDate)) {
+                    last = endDate;
+                }
+                for (LocalDate cur = d; !cur.isAfter(last); cur = cur.plusDays(1)) {
+                    if (cur.isBefore(startDate)) {
+                        continue;
+                    }
+                    byDate.computeIfAbsent(cur, k -> new java.util.ArrayList<>()).add(leave);
+                }
+            }
+            for (var entry : byDate.entrySet()) {
+                // 宸叉湁鑰冨嫟璁板綍鐨勬棩鏈燂紝璇峰亣宸ユ椂宸插湪 record.leaveHours 閲岀畻杩�
+                if (recordDates.contains(entry.getKey())) {
+                    continue;
+                }
+                leaveHours = leaveHours.add(calcLeaveHoursByDay(entry.getValue(), rule, entry.getKey()));
             }
         }
 
@@ -108,7 +168,7 @@
                     .earlyCount(earlyCount)
                     .absentCount(absentCount)
                     .overtimeHours(overtimeHours)
-                    .leaveHours(BigDecimal.ZERO)
+                    .leaveHours(leaveHours)
                     .build();
             attendanceSummaryMapper.insert(summary);
         } else {
@@ -118,6 +178,7 @@
             summary.setEarlyCount(earlyCount);
             summary.setAbsentCount(absentCount);
             summary.setOvertimeHours(overtimeHours);
+            summary.setLeaveHours(leaveHours); // 璇峰亣宸ユ椂涔熻鏇存柊锛屽惁鍒欓噸绠楁椂姘歌繙鍋滃湪涓婁竴娆�
             attendanceSummaryMapper.updateById(summary);
         }
     }
@@ -155,6 +216,43 @@
     /**
      * 璁$畻搴斿嚭鍕ゅぉ鏁帮紙宸ヤ綔鏃ユ暟閲忥級
      */
+    /**
+     * 璁$畻鏌愪竴澶╃殑璇峰亣宸ユ椂锛堜笌鑰冨嫟璁板綍閲岀殑绠楁硶淇濇寔涓�鑷达級
+     *
+     * <p>鍙栨瘡寮犺鍋囧崟涓庤�冨嫟瑙勫垯宸ヤ綔鏃舵鐨勪氦闆嗙疮鍔狅紝绮剧‘鍒� 0.01 灏忔椂銆�</p>
+     */
+    private BigDecimal calcLeaveHoursByDay(
+            List<cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO> leaves,
+            HrmAttendanceRuleDO rule, LocalDate date) {
+        LocalDateTime workStart;
+        LocalDateTime workEnd;
+        if (rule != null && rule.getWorkStartTime() != null && rule.getWorkEndTime() != null) {
+            workStart = LocalDateTime.of(date, rule.getWorkStartTime());
+            workEnd = LocalDateTime.of(date, rule.getWorkEndTime());
+            if (!workEnd.isAfter(workStart)) {
+                workEnd = workEnd.plusDays(1);
+            }
+        } else {
+            workStart = date.atStartOfDay();
+            workEnd = date.plusDays(1).atStartOfDay();
+        }
+        BigDecimal total = BigDecimal.ZERO;
+        for (var leave : leaves) {
+            if (leave.getStartTime() == null || leave.getEndTime() == null) {
+                continue;
+            }
+            LocalDateTime from = leave.getStartTime().isAfter(workStart) ? leave.getStartTime() : workStart;
+            LocalDateTime to = leave.getEndTime().isBefore(workEnd) ? leave.getEndTime() : workEnd;
+            if (!to.isAfter(from)) {
+                continue;
+            }
+            long minutes = java.time.Duration.between(from, to).toMinutes();
+            total = total.add(BigDecimal.valueOf(minutes)
+                    .divide(BigDecimal.valueOf(60), 2, java.math.RoundingMode.HALF_UP));
+        }
+        return total;
+    }
+
     private int calculateWorkDays(LocalDate startDate, LocalDate endDate) {
         int workDays = 0;
         LocalDate current = startDate;

--
Gitblit v1.9.3