gaoluyang
8 天以前 9e9f0e2d9f6fcce7dcea88b181b3c7e6069e8a1b
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
@@ -43,6 +43,7 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -358,7 +359,8 @@
        BigDecimal totalLeaveDeduction = BigDecimal.ZERO;
        for (HrmLeaveApplicationDO application : leaveApplications) {
            Integer leaveType = application.getLeaveType();
            BigDecimal duration = application.getDuration() != null ? application.getDuration() : BigDecimal.ZERO;
            // 跨月请假按落在本核算月内的天数折算,避免整条请假被跳过或被重复计入
            BigDecimal duration = resolveDurationInMonth(application, monthStart, monthEnd);
            BigDecimal deductionRatio = configMap.getOrDefault(leaveType, BigDecimal.ZERO);
            // 请假扣款 = 日工资 × 请假天数 × 扣款比例
@@ -371,6 +373,39 @@
    }
    /**
     * 计算一笔请假落在本核算月内的请假时长
     *
     * 1)请假整段都在本月内:直接取请假单上的时长;
     * 2)请假跨月:按「本月内占用的自然日 / 请假总自然日」折算,例如 10-28 ~ 11-03 共 7 天,
     *    其中 10 月占 4 天,则 10 月核算按 时长 × 4 / 7 计,11 月按 时长 × 3 / 7 计,避免重复或漏算。
     */
    private BigDecimal resolveDurationInMonth(HrmLeaveApplicationDO application,
                                             LocalDateTime monthStart, LocalDateTime monthEnd) {
        BigDecimal duration = application.getDuration() != null ? application.getDuration() : BigDecimal.ZERO;
        LocalDateTime start = application.getStartTime();
        LocalDateTime end = application.getEndTime();
        if (start == null || end == null) {
            return duration;
        }
        // 整段都在本月内
        if (!start.isBefore(monthStart) && !end.isAfter(monthEnd)) {
            return duration;
        }
        long totalDays = ChronoUnit.DAYS.between(start.toLocalDate(), end.toLocalDate()) + 1;
        if (totalDays <= 0) {
            return duration;
        }
        LocalDateTime overlapStart = start.isBefore(monthStart) ? monthStart : start;
        LocalDateTime overlapEnd = end.isAfter(monthEnd) ? monthEnd : end;
        if (overlapStart.isAfter(overlapEnd)) {
            return BigDecimal.ZERO;
        }
        long overlapDays = ChronoUnit.DAYS.between(overlapStart.toLocalDate(), overlapEnd.toLocalDate()) + 1;
        return duration.multiply(BigDecimal.valueOf(overlapDays))
                .divide(BigDecimal.valueOf(totalDays), 4, RoundingMode.HALF_UP);
    }
    /**
     * 汇总当月审批通过的加班申请。小时工资按基本工资 / 21.75 / 8 计算;
     * 工作日默认 1.5 倍、休息日默认 2 倍、法定节假日默认 3 倍,均可由薪酬结构覆盖。
     */