| | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; |
| | | import cn.iocoder.yudao.framework.operatelog.core.util.OperateLogUtils; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordImportExcelVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordImportReqVO; |
| | | 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.dal.dataobject.attendance.HrmAttendanceRecordDO; |
| | |
| | | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| | | import com.mzt.logapi.starter.annotation.LogRecord; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.EMPLOYEE_NOT_EXISTS; |
| | | import static cn.iocoder.yudao.module.hrm.enums.LogRecordConstants.*; |
| | | |
| | | @Tag(name = "管理后台 - HRM 考勤记录") |
| | | @RestController |
| | |
| | | |
| | | @PostMapping("/clock-in") |
| | | @Operation(summary = "上班打卡") |
| | | @PreAuthorize("@ss.hasPermission('hrm:attendance:clock')") |
| | | @PreAuthorize("@ss.hasPermission('hrm:attendance-record:clock')") |
| | | public CommonResult<HrmAttendanceRecordRespVO> clockIn(@RequestParam("location") String location) { |
| | | Long loginUserId = getLoginUserId(); |
| | | HrmEmployeeDO employee = employeeService.getEmployeeByUserId(loginUserId); |
| | |
| | | |
| | | @PostMapping("/clock-out") |
| | | @Operation(summary = "下班打卡") |
| | | @PreAuthorize("@ss.hasPermission('hrm:attendance:clock')") |
| | | @PreAuthorize("@ss.hasPermission('hrm:attendance-record:clock')") |
| | | public CommonResult<HrmAttendanceRecordRespVO> clockOut(@RequestParam("location") String location) { |
| | | Long loginUserId = getLoginUserId(); |
| | | HrmEmployeeDO employee = employeeService.getEmployeeByUserId(loginUserId); |
| | |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "获得考勤记录分页") |
| | | @PreAuthorize("@ss.hasPermission('hrm:attendance:query')") |
| | | @PreAuthorize("@ss.hasPermission('hrm:attendance-record:query')") |
| | | public CommonResult<PageResult<HrmAttendanceRecordRespVO>> getAttendanceRecordPage(@Valid HrmAttendanceRecordPageReqVO pageReqVO) { |
| | | PageResult<HrmAttendanceRecordDO> pageResult = attendanceRecordService.getAttendanceRecordPage(pageReqVO); |
| | | // 转换为 VO |
| | |
| | | HrmEmployeeDO employee = employeeMap.get(respVO.getUserId()); |
| | | if (employee != null) { |
| | | respVO.setUserName(employee.getName()); |
| | | respVO.setPhone(employee.getPhone()); |
| | | } |
| | | // 部门名称 |
| | | DeptRespDTO dept = deptMap.get(respVO.getDeptId()); |
| | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "获得考勤记录") |
| | | @Parameter(name = "id", description = "记录ID", required = true) |
| | | @PreAuthorize("@ss.hasPermission('hrm:attendance:query')") |
| | | @PreAuthorize("@ss.hasPermission('hrm:attendance-record:query')") |
| | | public CommonResult<HrmAttendanceRecordRespVO> getAttendanceRecord(@RequestParam("id") Long id) { |
| | | HrmAttendanceRecordDO attendanceRecord = attendanceRecordService.getAttendanceRecord(id); |
| | | if (attendanceRecord == null) { |
| | |
| | | HrmEmployeeDO employee = employeeService.getEmployee(attendanceRecord.getUserId()); |
| | | if (employee != null) { |
| | | respVO.setUserName(employee.getName()); |
| | | respVO.setPhone(employee.getPhone()); |
| | | } |
| | | } |
| | | if (attendanceRecord.getDeptId() != null) { |
| | |
| | | return success(respVO); |
| | | } |
| | | |
| | | @GetMapping("/get-import-template") |
| | | @Operation(summary = "下载考勤记录导入模板") |
| | | @PreAuthorize("@ss.hasPermission('hrm:attendance-record:import')") |
| | | public void importTemplate(HttpServletResponse response) throws IOException { |
| | | List<HrmAttendanceRecordImportExcelVO> list = Collections.singletonList( |
| | | HrmAttendanceRecordImportExcelVO.builder() |
| | | .userName("张三") |
| | | .phone("13800138000") |
| | | .date(java.time.LocalDate.of(2026, 1, 15)) |
| | | .clockInTime(java.time.LocalDateTime.of(2026, 1, 15, 8, 30)) |
| | | .clockOutTime(java.time.LocalDateTime.of(2026, 1, 15, 17, 30)) |
| | | .clockInType(1) |
| | | .clockOutType(1) |
| | | .location("办公区") |
| | | .remark("示例数据") |
| | | .build() |
| | | ); |
| | | ExcelUtils.write(response, "考勤导入模板.xls", "考勤记录", HrmAttendanceRecordImportExcelVO.class, list); |
| | | } |
| | | |
| | | @PostMapping("/import") |
| | | @Operation(summary = "导入考勤记录") |
| | | @PreAuthorize("@ss.hasPermission('hrm:attendance-record:import')") |
| | | @LogRecord(type = HRM_ATTENDANCE_RECORD_TYPE, subType = HRM_ATTENDANCE_RECORD_IMPORT_SUB_TYPE, bizNo = "0", |
| | | success = HRM_ATTENDANCE_RECORD_IMPORT_SUCCESS) |
| | | public CommonResult<String> importExcel(@Valid HrmAttendanceRecordImportReqVO importReqVO) throws IOException { |
| | | List<HrmAttendanceRecordImportExcelVO> list = ExcelUtils.read(importReqVO.getFile(), HrmAttendanceRecordImportExcelVO.class); |
| | | if (CollUtil.isEmpty(list)) { |
| | | // 记录导入结果汇总,供 @LogRecord 的 success 文案引用 |
| | | OperateLogUtils.putImportSummary(0, null, null); |
| | | return success("没有可导入的数据"); |
| | | } |
| | | int successCount = 0; |
| | | int createCount = 0; // 新增的记录数 |
| | | int updateCount = 0; // 更新的记录数(仅勾选「支持更新」时才会走更新) |
| | | Set<String> missingNames = new LinkedHashSet<>(); |
| | | Set<String> duplicateNames = new LinkedHashSet<>(); |
| | | Set<String> invalidRows = new LinkedHashSet<>(); |
| | | for (HrmAttendanceRecordImportExcelVO item : list) { |
| | | if (item.getDate() == null) { |
| | | invalidRows.add((item.getUserName() == null || item.getUserName().isBlank()) ? "未填姓名的行" : "考勤日期为空(" + item.getUserName().trim() + ")"); |
| | | continue; |
| | | } |
| | | if (item.getUserName() == null || item.getUserName().isBlank()) { |
| | | invalidRows.add("考勤日期为 " + item.getDate() + " 但未填员工姓名"); |
| | | continue; |
| | | } |
| | | List<HrmEmployeeDO> employees = employeeService.getEmployeeListByName(item.getUserName().trim()); |
| | | if (employees.isEmpty()) { |
| | | missingNames.add(item.getUserName().trim()); |
| | | continue; |
| | | } |
| | | HrmEmployeeDO employee = employees.get(0); |
| | | // 存在多个同名员工时,用手机号区分 |
| | | if (employees.size() > 1) { |
| | | String phone = item.getPhone() != null ? item.getPhone().trim() : null; |
| | | List<HrmEmployeeDO> matchedByPhone = new ArrayList<>(); |
| | | if (phone != null && !phone.isEmpty()) { |
| | | for (HrmEmployeeDO emp : employees) { |
| | | if (phone.equals(emp.getPhone())) { |
| | | matchedByPhone.add(emp); |
| | | } |
| | | } |
| | | } |
| | | if (matchedByPhone.size() == 1) { |
| | | employee = matchedByPhone.get(0); |
| | | } else { |
| | | duplicateNames.add(item.getUserName().trim()); |
| | | continue; |
| | | } |
| | | } |
| | | HrmAttendanceRecordDO record = attendanceRecordService.getAttendanceRecordByUserIdAndDate(employee.getId(), item.getDate()); |
| | | if (record == null) { |
| | | record = HrmAttendanceRecordDO.builder() |
| | | .userId(employee.getId()).deptId(employee.getDeptId()).date(item.getDate()) |
| | | .clockInTime(item.getClockInTime()).clockOutTime(item.getClockOutTime()) |
| | | .clockInType(item.getClockInType()).clockOutType(item.getClockOutType()) |
| | | .location(item.getLocation()).remark(item.getRemark()) |
| | | .dataSource(2).build(); |
| | | attendanceRecordService.createImportedRecord(record); |
| | | createCount++; |
| | | } else if (Boolean.TRUE.equals(importReqVO.getUpdateSupport())) { |
| | | record.setClockInTime(item.getClockInTime()); |
| | | record.setClockOutTime(item.getClockOutTime()); |
| | | record.setClockInType(item.getClockInType()); |
| | | record.setClockOutType(item.getClockOutType()); |
| | | record.setLocation(item.getLocation()); |
| | | record.setRemark(item.getRemark()); |
| | | attendanceRecordService.updateImportedRecord(record); |
| | | updateCount++; |
| | | } |
| | | successCount++; |
| | | } |
| | | StringBuilder msg = new StringBuilder("成功导入 " + successCount + " 条"); |
| | | if (!missingNames.isEmpty()) { |
| | | msg.append(";以下员工不存在,未能导入:").append(String.join("、", missingNames)); |
| | | } |
| | | if (!duplicateNames.isEmpty()) { |
| | | msg.append(";以下员工存在多名同名员工,需补充手机号区分,未能导入:").append(String.join("、", duplicateNames)); |
| | | } |
| | | if (!invalidRows.isEmpty()) { |
| | | msg.append(";以下记录数据不完整,未能导入:").append(String.join("、", invalidRows)); |
| | | } |
| | | // 记录导入结果汇总,供 @LogRecord 的 success 文案引用 |
| | | OperateLogUtils.putImportSummary(createCount, updateCount, |
| | | missingNames.size() + duplicateNames.size() + invalidRows.size()); |
| | | return success(msg.toString()); |
| | | } |
| | | |
| | | @GetMapping("/export-excel") |
| | | @Operation(summary = "导出考勤记录 Excel") |
| | | @PreAuthorize("@ss.hasPermission('hrm:attendance:export')") |
| | | @PreAuthorize("@ss.hasPermission('hrm:attendance-record:export')") |
| | | @ApiAccessLog(operateType = EXPORT) |
| | | public void exportAttendanceRecordExcel(@Valid HrmAttendanceRecordPageReqVO pageReqVO, |
| | | HttpServletResponse response) throws IOException { |