From 962f78c8383e7c884ddb08c951c15e5ba6eb4cb8 Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期五, 23 十月 2026 15:43:33 +0800
Subject: [PATCH] fix: 完善审批流程与业务状态校验
---
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java | 312 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 310 insertions(+), 2 deletions(-)
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java
index 6eab672..3780f24 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java
@@ -2,7 +2,11 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceMyStatusRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleRespVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceHolidayDO;
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.HrmAttendanceExceptionDO;
@@ -11,14 +15,19 @@
import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceClockTypeEnum;
import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceDataSourceEnum;
import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceExceptionTypeEnum;
+import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceHolidayTypeEnum;
import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.math.RoundingMode;
+import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -31,10 +40,11 @@
/**
* 鑰冨嫟璁板綍 Service 瀹炵幇绫�
*
- * @author 鑺嬮亾婧愮爜
+ * @author 瓒呯骇绠$悊鍛�
*/
@Service
@Validated
+@Slf4j
public class HrmAttendanceRecordServiceImpl implements HrmAttendanceRecordService {
@Resource
@@ -45,6 +55,8 @@
private HrmAttendanceRuleService attendanceRuleService;
@Resource
private HrmAttendanceHolidayService attendanceHolidayService;
+ @Resource
+ private DeptApi deptApi;
@Override
public PageResult<HrmAttendanceRecordDO> getAttendanceRecordPage(HrmAttendanceRecordPageReqVO pageReqVO) {
@@ -64,6 +76,242 @@
@Override
public HrmAttendanceRecordDO getAttendanceRecordByUserIdAndDate(Long userId, LocalDate date) {
return attendanceRecordMapper.selectByUserIdAndDate(userId, date);
+ }
+
+ @Override
+ public HrmAttendanceMyStatusRespVO getMyStatus(Long loginUserId, LocalDate date) {
+ LocalDate queryDate = date != null ? date : LocalDate.now();
+ // 1. 鐧诲綍鐢ㄦ埛 -> 鍛樺伐妗f
+ HrmEmployeeDO employee = employeeService.getEmployeeByUserId(loginUserId);
+ if (employee == null) {
+ throw exception(EMPLOYEE_NOT_EXISTS,
+ "褰撳墠鐧诲綍璐﹀彿鏈叧鑱斿憳宸ユ。妗堬紝鏃犳硶鎵撳崱銆傝鑱旂郴绠$悊鍛樺湪銆愬憳宸ョ鐞嗐�戜腑灏嗚鐧诲綍璐﹀彿缁戝畾鍒板憳宸ャ��");
+ }
+ HrmAttendanceMyStatusRespVO respVO = new HrmAttendanceMyStatusRespVO();
+ respVO.setUserId(loginUserId);
+ respVO.setEmployeeId(employee.getId());
+ respVO.setEmployeeName(employee.getName());
+ respVO.setDeptId(employee.getDeptId());
+ if (employee.getDeptId() != null) {
+ DeptRespDTO dept = deptApi.getDept(employee.getDeptId());
+ if (dept != null) {
+ respVO.setDeptName(dept.getName());
+ }
+ }
+ respVO.setDate(queryDate);
+ respVO.setServerTime(LocalDateTime.now());
+
+ // 2. 鏄惁宸ヤ綔鏃� + 浼戞伅鍘熷洜
+ boolean workDay = attendanceHolidayService.isWorkDay(queryDate);
+ respVO.setWorkDay(workDay);
+ if (!workDay) {
+ respVO.setRestDayReason(getRestDayReason(queryDate));
+ }
+
+ // 3. 褰撳墠鐢熸晥瑙勫垯锛堥儴闂ㄨ鍒欎紭鍏堬紝鍏舵鍏ㄥ眬瑙勫垯锛�
+ HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId());
+ if (rule != null) {
+ HrmAttendanceRuleRespVO ruleRespVO = BeanUtils.toBean(rule, HrmAttendanceRuleRespVO.class);
+ if (rule.getDeptId() != null) {
+ DeptRespDTO ruleDept = deptApi.getDept(rule.getDeptId());
+ if (ruleDept != null) {
+ ruleRespVO.setDeptName(ruleDept.getName());
+ }
+ }
+ respVO.setRule(ruleRespVO);
+ }
+
+ // 4. 褰撴棩鑰冨嫟璁板綍
+ HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(employee.getId(), queryDate);
+ if (record != null) {
+ respVO.setRecord(buildRecordRespVO(record, employee));
+ }
+
+ // 5. 褰撳墠鍙墦鍗$姸鎬�
+ respVO.setClockable(buildClockable(workDay, rule, record, queryDate));
+ return respVO;
+ }
+
+ /**
+ * 浼戞伅鏃ュ師鍥狅細浼樺厛鍙栬妭鍋囨棩閰嶇疆鍚嶇О锛屾湭閰嶇疆鏃舵寜鏄熸湡缁欏嚭鈥滃懆鍏�/鍛ㄦ棩鈥�
+ */
+ private String getRestDayReason(LocalDate date) {
+ List<HrmAttendanceHolidayDO> holidays = attendanceHolidayService.getAttendanceHolidayListByDateRange(date, date);
+ for (HrmAttendanceHolidayDO holiday : holidays) {
+ if (HrmAttendanceHolidayTypeEnum.REST_DAY.getType().equals(holiday.getType())) {
+ return holiday.getName();
+ }
+ }
+ DayOfWeek dayOfWeek = date.getDayOfWeek();
+ if (dayOfWeek == DayOfWeek.SATURDAY) {
+ return "鍛ㄥ叚";
+ }
+ if (dayOfWeek == DayOfWeek.SUNDAY) {
+ return "鍛ㄦ棩";
+ }
+ return null;
+ }
+
+ /**
+ * 鏋勫缓鎵撳崱璁板綍杩斿洖 VO锛岃ˉ榻愭墦鍗$被鍨嬪悕绉般�佸憳宸ヤ笌閮ㄩ棬淇℃伅
+ */
+ private HrmAttendanceRecordRespVO buildRecordRespVO(HrmAttendanceRecordDO record, HrmEmployeeDO employee) {
+ HrmAttendanceRecordRespVO recordRespVO = BeanUtils.toBean(record, HrmAttendanceRecordRespVO.class);
+ recordRespVO.setClockInTypeName(HrmAttendanceClockTypeEnum.getNameByType(record.getClockInType()));
+ recordRespVO.setClockOutTypeName(HrmAttendanceClockTypeEnum.getNameByType(record.getClockOutType()));
+ if (employee != null) {
+ recordRespVO.setUserName(employee.getName());
+ recordRespVO.setPhone(employee.getPhone());
+ }
+ if (record.getDeptId() != null) {
+ DeptRespDTO dept = deptApi.getDept(record.getDeptId());
+ if (dept != null) {
+ recordRespVO.setDeptName(dept.getName());
+ }
+ }
+ return recordRespVO;
+ }
+
+ /**
+ * 鏍规嵁宸ヤ綔鏃ャ�佽�冨嫟瑙勫垯涓庡綋鏃ヨ褰曡绠楀彲鎵撳崱鐘舵��
+ */
+ private HrmAttendanceMyStatusRespVO.Clockable buildClockable(boolean workDay, HrmAttendanceRuleDO rule,
+ HrmAttendanceRecordDO record, LocalDate date) {
+ HrmAttendanceMyStatusRespVO.Clockable clockable = new HrmAttendanceMyStatusRespVO.Clockable();
+ if (!workDay) {
+ clockable.setClockIn(false);
+ clockable.setClockOut(false);
+ clockable.setNextAction("none");
+ clockable.setMessage("浠婃棩浼戞伅锛屾棤闇�鎵撳崱");
+ return clockable;
+ }
+ if (rule == null) {
+ clockable.setClockIn(false);
+ clockable.setClockOut(false);
+ clockable.setNextAction("none");
+ clockable.setMessage("鏈厤缃�冨嫟瑙勫垯锛岃鑱旂郴绠$悊鍛�");
+ return clockable;
+ }
+ // 鍙湁涓嬬彮鍗°�佹病鏈変笂鐝崱锛氬綋鏃ュ凡缁撴潫锛屼笉鍐嶅厑璁哥户缁墦鍗�
+ if (record != null && record.getClockInTime() == null && record.getClockOutTime() != null) {
+ clockable.setClockIn(false);
+ clockable.setClockOut(false);
+ clockable.setNextAction("none");
+ clockable.setMessage("浠婃棩缂轰笂鐝崱锛屽凡瀹屾垚涓嬬彮鎵撳崱");
+ return clockable;
+ }
+ // 浠婂ぉ杩樻病鎵撲笂鐝崱
+ if (record == null || record.getClockInTime() == null) {
+ clockable.setClockIn(true);
+ clockable.setClockOut(false);
+ clockable.setNextAction("in");
+ clockable.setMessage("鍙笂鐝墦鍗�");
+ // 浠呭綋澶╂煡璇㈡椂鎻愮ず宸茶繜鍒版椂闀�
+ LocalDateTime now = LocalDateTime.now();
+ if (rule.getWorkStartTime() != null && date.equals(now.toLocalDate())) {
+ LocalDateTime workStart = LocalDateTime.of(date, rule.getWorkStartTime());
+ int lateAllowMinutes = rule.getLateAllowMinutes() != null ? rule.getLateAllowMinutes() : 0;
+ if (now.isAfter(workStart.plusMinutes(lateAllowMinutes))) {
+ long lateMinutes = Duration.between(workStart, now).toMinutes();
+ clockable.setMessage("宸茶秴杩囦笂鐝椂闂� " + lateMinutes + " 鍒嗛挓");
+ }
+ }
+ return clockable;
+ }
+ // 宸叉墦涓婄彮鍗°�佹湭鎵撲笅鐝崱
+ if (record.getClockOutTime() == null) {
+ clockable.setClockIn(false);
+ clockable.setClockOut(true);
+ clockable.setNextAction("out");
+ clockable.setMessage("宸蹭笂鐝墦鍗★紝鍙笅鐝墦鍗�");
+ return clockable;
+ }
+ // 涓婁笅鐝崱閮藉凡鎵�
+ clockable.setClockIn(false);
+ clockable.setClockOut(false);
+ clockable.setNextAction("none");
+ clockable.setMessage("浠婃棩宸插畬鎴愭墦鍗�");
+ return clockable;
+ }
+
+ @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);
+ }
+
+ /**
+ * 鎸夎�冨嫟瑙勫垯閲嶇畻涓婁笅鐝墦鍗$被鍨嬶紙姝e父/杩熷埌/鏃╅��锛夛紝涓庢墦鍗¢�昏緫淇濇寔涓�鑷�
+ */
+ 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
@@ -173,8 +421,59 @@
.divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
record.setWorkHours(workHours);
}
+ // 7. 璁$畻鍔犵彮鏃堕暱锛氳秴杩囪鍒欎笅鐝椂闂寸殑閮ㄥ垎
+ record.setOvertimeHours(computeOvertimeHours(rule.getWorkEndTime(), clockTimeOnly));
attendanceRecordMapper.updateById(record);
return record;
+ }
+
+ /**
+ * 璁$畻鍔犵彮鏃堕暱锛堝皬鏃讹級锛歝lockTimeOnly 鏅氫簬 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
@@ -186,12 +485,19 @@
}
LocalDate exceptionDate = exception.getExceptionDate();
LocalDateTime exceptionTime = exception.getExceptionTime();
+ // 琛ュ崱鏃堕棿缂哄け鏃舵棤娉曞洖濉�冨嫟璁板綍锛岃褰曟棩蹇楀苟璺宠繃锛岄伩鍏嶇┖鎸囬拡
+ if (exceptionTime == null) {
+ log.warn("[supplementClockIn] 琛ュ崱鐢宠({}) 琛ュ崱鏃堕棿涓虹┖锛岃烦杩囪�冨嫟璁板綍鍥炲~", exception.getId());
+ return;
+ }
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())
@@ -210,6 +516,8 @@
// 涓嬬彮琛ュ崱
record.setClockOutTime(exceptionTime);
record.setClockOutType(HrmAttendanceClockTypeEnum.NORMAL.getType());
+ // 涓嬬彮琛ュ崱鍚屾椂鎶樼畻鍔犵彮鏃堕暱锛堣秴杩囪鍒欎笅鐝椂闂撮儴鍒嗭級
+ record.setOvertimeHours(computeOvertimeHours(rule == null ? null : rule.getWorkEndTime(), time));
}
// 鏇存柊鏁版嵁鏉ユ簮涓鸿ˉ鍗″鎵�
record.setDataSource(HrmAttendanceDataSourceEnum.SUPPLEMENT.getType());
--
Gitblit v1.9.3