| | |
| | | |
| | | 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; |
| | |
| | | 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 org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.DayOfWeek; |
| | | import java.time.Duration; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | |
| | | private HrmAttendanceRuleService attendanceRuleService; |
| | | @Resource |
| | | private HrmAttendanceHolidayService attendanceHolidayService; |
| | | @Resource |
| | | private DeptApi deptApi; |
| | | |
| | | @Override |
| | | public PageResult<HrmAttendanceRecordDO> getAttendanceRecordPage(HrmAttendanceRecordPageReqVO pageReqVO) { |
| | |
| | | } |
| | | |
| | | @Override |
| | | public HrmAttendanceMyStatusRespVO getMyStatus(Long loginUserId, LocalDate date) { |
| | | LocalDate queryDate = date != null ? date : LocalDate.now(); |
| | | // 1. 登录用户 -> 员工档案 |
| | | 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); |