| | |
| | | } |
| | | |
| | | @Override |
| | | public void createImportedRecord(HrmAttendanceRecordDO record) { |
| | | // 按考勤规则重算打卡类型与时长,不信任 Excel 填写的值 |
| | | fillClockTypes(record); |
| | | fillCalculatedHours(record); |
| | | attendanceRecordMapper.insert(record); |
| | | } |
| | | |
| | | @Override |
| | | public void updateImportedRecord(HrmAttendanceRecordDO record) { |
| | | fillClockTypes(record); |
| | | fillCalculatedHours(record); |
| | | attendanceRecordMapper.updateById(record); |
| | | } |
| | | |
| | | /** |
| | | * 按考勤规则重算上下班打卡类型(正常/迟到/早退),与打卡逻辑保持一致 |
| | | */ |
| | | private void fillClockTypes(HrmAttendanceRecordDO record) { |
| | | if (record.getDeptId() == null) { |
| | | return; |
| | | } |
| | | HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(record.getDeptId()); |
| | | if (rule == null) { |
| | | return; |
| | | } |
| | | // 上班打卡类型:晚于 上班时间+迟到允许 记为迟到 |
| | | if (record.getClockInTime() != null && rule.getWorkStartTime() != null) { |
| | | int lateAllowMinutes = rule.getLateAllowMinutes() != null ? rule.getLateAllowMinutes() : 0; |
| | | LocalTime lateThreshold = rule.getWorkStartTime().plusMinutes(lateAllowMinutes); |
| | | LocalTime clockInOnly = record.getClockInTime().toLocalTime(); |
| | | record.setClockInType(clockInOnly.isAfter(lateThreshold) |
| | | ? HrmAttendanceClockTypeEnum.LATE.getType() |
| | | : HrmAttendanceClockTypeEnum.NORMAL.getType()); |
| | | } |
| | | // 下班打卡类型:早于 下班时间-早退允许 记为早退 |
| | | if (record.getClockOutTime() != null && rule.getWorkEndTime() != null) { |
| | | int earlyAllowMinutes = rule.getEarlyAllowMinutes() != null ? rule.getEarlyAllowMinutes() : 0; |
| | | LocalTime earlyThreshold = rule.getWorkEndTime().minusMinutes(earlyAllowMinutes); |
| | | LocalTime clockOutOnly = record.getClockOutTime().toLocalTime(); |
| | | record.setClockOutType(clockOutOnly.isBefore(earlyThreshold) |
| | | ? HrmAttendanceClockTypeEnum.EARLY.getType() |
| | | : HrmAttendanceClockTypeEnum.NORMAL.getType()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据上下班打卡时间和考勤规则,计算工作时长与加班时长 |
| | | */ |
| | | private void fillCalculatedHours(HrmAttendanceRecordDO record) { |
| | | // 工作时长 = 下班打卡时间 - 上班打卡时间(与打卡逻辑一致) |
| | | if (record.getClockInTime() != null && record.getClockOutTime() != null) { |
| | | Duration duration = Duration.between(record.getClockInTime(), record.getClockOutTime()); |
| | | BigDecimal workHours = BigDecimal.valueOf(duration.toMinutes()) |
| | | .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP); |
| | | record.setWorkHours(workHours); |
| | | } |
| | | // 加班时长:按考勤规则的标准下班时间计算超额部分 |
| | | if (record.getClockOutTime() != null && record.getDeptId() != null) { |
| | | HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(record.getDeptId()); |
| | | if (rule != null && rule.getWorkEndTime() != null) { |
| | | LocalTime workEnd = rule.getWorkEndTime(); |
| | | LocalDate outDate = record.getClockOutTime().toLocalDate(); |
| | | LocalDateTime workEndDateTime = LocalDateTime.of(outDate, workEnd); |
| | | if (record.getClockOutTime().isAfter(workEndDateTime)) { |
| | | Duration overtimeDuration = Duration.between(workEndDateTime, record.getClockOutTime()); |
| | | BigDecimal overtimeThreshold = rule.getOvertimeThreshold() != null |
| | | ? rule.getOvertimeThreshold() : BigDecimal.ZERO; |
| | | BigDecimal overtimeHours = BigDecimal.valueOf(overtimeDuration.toMinutes()) |
| | | .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP); |
| | | record.setOvertimeHours(overtimeHours.compareTo(overtimeThreshold) > 0 ? overtimeHours : BigDecimal.ZERO); |
| | | } else { |
| | | record.setOvertimeHours(BigDecimal.ZERO); |
| | | } |
| | | } else { |
| | | record.setOvertimeHours(BigDecimal.ZERO); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public HrmAttendanceRecordDO clockIn(Long userId, LocalDateTime clockTime, String location) { |
| | | LocalDate clockDate = clockTime.toLocalDate(); |
| | |
| | | .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP); |
| | | record.setWorkHours(workHours); |
| | | } |
| | | // 7. 计算加班时长:超过规则下班时间的部分 |
| | | record.setOvertimeHours(computeOvertimeHours(rule.getWorkEndTime(), clockTimeOnly)); |
| | | attendanceRecordMapper.updateById(record); |
| | | return record; |
| | | } |
| | | |
| | | /** |
| | | * 计算加班时长(小时):clockTimeOnly 晚于 workEndTime 的部分,否则为 0 |
| | | */ |
| | | private BigDecimal computeOvertimeHours(LocalTime workEndTime, LocalTime clockTimeOnly) { |
| | | if (workEndTime == null || clockTimeOnly == null || !clockTimeOnly.isAfter(workEndTime)) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | return BigDecimal.valueOf(Duration.between(workEndTime, clockTimeOnly).toMinutes()) |
| | | .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void recordOvertime(HrmAttendanceExceptionDO exception) { |
| | | // 仅处理加班类型 |
| | | if (!HrmAttendanceExceptionTypeEnum.OVERTIME.getType().equals(exception.getType()) |
| | | || exception.getExceptionTime() == null) { |
| | | return; |
| | | } |
| | | // 1. 取员工考勤规则,用规则下班时间到异常时间(实际下班点)折算加班时长;无规则则无法折算,跳过 |
| | | BigDecimal overtimeHours = BigDecimal.ZERO; |
| | | HrmEmployeeDO employee = employeeService.getEmployee(exception.getUserId()); |
| | | HrmAttendanceRuleDO rule = employee == null ? null |
| | | : attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId()); |
| | | if (rule != null) { |
| | | overtimeHours = computeOvertimeHours(rule.getWorkEndTime(), exception.getExceptionTime().toLocalTime()); |
| | | } |
| | | if (overtimeHours.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return; |
| | | } |
| | | // 2. 查询当日考勤记录,不存在则补建 |
| | | HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(exception.getUserId(), exception.getExceptionDate()); |
| | | if (record == null) { |
| | | record = HrmAttendanceRecordDO.builder() |
| | | .userId(exception.getUserId()) |
| | | .deptId(employee != null ? employee.getDeptId() : exception.getDeptId()) |
| | | .date(exception.getExceptionDate()) |
| | | .dataSource(HrmAttendanceDataSourceEnum.SUPPLEMENT.getType()) |
| | | .build(); |
| | | record.setOvertimeHours(overtimeHours); |
| | | attendanceRecordMapper.insert(record); |
| | | return; |
| | | } |
| | | // 3. 已有记录则累加加班时长 |
| | | BigDecimal existed = record.getOvertimeHours() != null ? record.getOvertimeHours() : BigDecimal.ZERO; |
| | | record.setOvertimeHours(existed.add(overtimeHours)); |
| | | attendanceRecordMapper.updateById(record); |
| | | } |
| | | |
| | | @Override |
| | |
| | | LocalDate exceptionDate = exception.getExceptionDate(); |
| | | LocalDateTime exceptionTime = exception.getExceptionTime(); |
| | | Long userId = exception.getUserId(); |
| | | HrmEmployeeDO employee = employeeService.getEmployee(userId); |
| | | HrmAttendanceRuleDO rule = employee == null ? null |
| | | : attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId()); |
| | | // 查询考勤记录 |
| | | HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(userId, exceptionDate); |
| | | if (record == null) { |
| | | // 如果没有记录,创建一条 |
| | | HrmEmployeeDO employee = employeeService.getEmployee(userId); |
| | | record = HrmAttendanceRecordDO.builder() |
| | | .userId(userId) |
| | | .deptId(employee != null ? employee.getDeptId() : exception.getDeptId()) |
| | |
| | | // 下班补卡 |
| | | record.setClockOutTime(exceptionTime); |
| | | record.setClockOutType(HrmAttendanceClockTypeEnum.NORMAL.getType()); |
| | | // 下班补卡同时折算加班时长(超过规则下班时间部分) |
| | | record.setOvertimeHours(computeOvertimeHours(rule == null ? null : rule.getWorkEndTime(), time)); |
| | | } |
| | | // 更新数据来源为补卡审批 |
| | | record.setDataSource(HrmAttendanceDataSourceEnum.SUPPLEMENT.getType()); |