gaoluyang
6 天以前 9e9f0e2d9f6fcce7dcea88b181b3c7e6069e8a1b
fix(hrm): 请假跨月按天分摊扣款,加班工资按薪酬结构倍率计算

- 请假查询改为按时间范围交集,跨月请假按本月占用天数折算,10 月/11 月分别扣各自天数,避免整条被跳过或重复计入
- 加班工资 = 基本工资 ÷ 21.75 ÷ 8 × 时长 × 倍率,倍率取员工绑定的薪酬结构,未绑定走默认 1.5/2/3

Co-Authored-By: Claude Code <noreply@anthropic.com>
已修改2个文件
50 ■■■■■ 文件已修改
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmLeaveApplicationMapper.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmLeaveApplicationMapper.java
@@ -39,19 +39,22 @@
    }
    /**
     * 查询员工在指定时间范围内已审批通过的请假记录
     * 查询员工与指定时间范围有交集的已审批通过的请假记录
     *
     * 注意:按「交集」而不是「完全包含」查询。跨月请假(如 10-28 ~ 11-03)两端都不满足完全包含,
     * 会被整条漏掉;是否按天分摊由调用方处理。
     *
     * @param userId    员工ID(系统用户ID)
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @param startTime 范围开始时间
     * @param endTime   范围结束时间
     * @return 请假记录列表
     */
    default List<HrmLeaveApplicationDO> selectApprovedListByUserIdAndTimeRange(Long userId, LocalDateTime startTime, LocalDateTime endTime) {
        return selectList(new LambdaQueryWrapperX<HrmLeaveApplicationDO>()
                .eq(HrmLeaveApplicationDO::getUserId, userId)
                .eq(HrmLeaveApplicationDO::getStatus, HrmAuditStatusEnum.APPROVE.getStatus())
                .ge(HrmLeaveApplicationDO::getStartTime, startTime)
                .le(HrmLeaveApplicationDO::getEndTime, endTime));
                .le(HrmLeaveApplicationDO::getStartTime, endTime)
                .ge(HrmLeaveApplicationDO::getEndTime, startTime));
    }
}
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 倍,均可由薪酬结构覆盖。
     */