diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/controller/AdminAttendanceController.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/controller/AdminAttendanceController.java index 5b75a14..591b212 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/controller/AdminAttendanceController.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/controller/AdminAttendanceController.java @@ -23,7 +23,7 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api/admin/attendance") +@RequestMapping("/api") @Tag(name = "관리자 출석관리", description = "관리자용 출석 관리 API") public class AdminAttendanceController { @@ -31,127 +31,172 @@ public class AdminAttendanceController { // 출석체크 시작 @Operation(summary = "출석 체크 시작", description = "새로운 출석 코드를 생성하고 출석 체크를 시작합니다.") - @ApiResponses({ - @io.swagger.v3.oas.annotations.responses.ApiResponse( - responseCode = "200", - description = "출석 코드 생성 성공", - content = @Content(schema = @Schema(implementation = AttendanceCodeResponse.class)) - ), + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "출석 코드 생성 성공"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청") }) - @PostMapping("/start") - public ApiResponse startAttendance() { + @PostMapping("/admin/attendance/start") + public AttendanceCodeResponse startAttendance() { try { AttendanceCode code = attendanceService.generateCodeAndCreateAttendances(); - return ApiResponse.success(AttendanceCodeResponse.from(code)); + return AttendanceCodeResponse.from(code); } catch (IllegalStateException e) { // 하루 최대 출석 체크 횟수를 초과한 경우 - return ApiResponse.error(e.getMessage()); + throw new IllegalStateException(e.getMessage()); } catch (Exception e) { - return ApiResponse.error("출석 코드 생성 중 오류가 발생했습니다: " + e.getMessage()); + throw new RuntimeException("출석 코드 생성 중 오류가 발생했습니다: " + e.getMessage()); } } // 현재 활성화된 출석코드 조회 @Operation(summary = "현재 활성화된 출석 코드 조회", description = "현재 활성화된 출석 코드 정보를 조회합니다.") - @ApiResponses({ - @io.swagger.v3.oas.annotations.responses.ApiResponse( - responseCode = "200", - description = "조회 성공", - content = @Content(schema = @Schema(implementation = AttendanceCodeResponse.class)) - ), + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "활성화된 출석 코드 없음") }) - @GetMapping("/active-code") - public ApiResponse getActiveCode() { + @GetMapping("/admin/attendance/active-code") + public AttendanceCodeResponse getActiveCode() { Optional codeOpt = attendanceService.getActiveAttendanceCode(); if (codeOpt.isEmpty()) { - return ApiResponse.error("현재 활성화된 출석코드가 없습니다"); + throw new RuntimeException("현재 활성화된 출석코드가 없습니다"); } - return ApiResponse.success(AttendanceCodeResponse.from(codeOpt.get())); + return AttendanceCodeResponse.from(codeOpt.get()); } // 출석체크 종료 (코드 직접 전달) @Operation(summary = "특정 출석 코드 만료", description = "특정 출석 코드를 만료 처리합니다.") - @ApiResponses({ + @ApiResponses(value = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "만료 처리 성공"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "출석 코드를 찾을 수 없음") }) - @PutMapping("/expire") - public ApiResponse expireAttendance( - @Parameter(description = "만료할 출석 코드", required = true) + @PutMapping("/admin/attendance/expire") + public String expireAttendance( + @Parameter(description = "만료할 출석 코드", example = "1234") @RequestParam String code) { - String result = attendanceService.expireAttendanceCode(code); - - if (result.equals("출석 코드가 성공적으로 만료되었습니다")) { - return ApiResponse.success(result, null); - } else { - return ApiResponse.error(result); - } + return attendanceService.expireAttendanceCode(code); } // 출석체크 종료 (가장 최근 활성화된 코드 자동 만료) @Operation(summary = "최근 활성화된 출석 코드 만료", description = "가장 최근 활성화된 출석 코드를 자동으로 만료 처리합니다.") - @ApiResponses({ + @ApiResponses(value = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "만료 처리 성공"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "활성화된 출석 코드가 없음") }) - @PutMapping("/expire-latest") - public ApiResponse expireLatestAttendance() { - String result = attendanceService.expireLatestAttendanceCode(); - - if (result.equals("출석 코드가 성공적으로 만료되었습니다")) { - return ApiResponse.success(result, null); - } else { - return ApiResponse.error(result); - } + @PutMapping("/admin/attendance/expire-latest") + public String expireLatestAttendance() { + return attendanceService.expireLatestAttendanceCode(); } // 출석 상태 변경 (관리자 전용) @Operation(summary = "출석 상태 변경", description = "관리자가 특정 사용자의 출석 상태를 변경합니다.") - @ApiResponses({ + @ApiResponses(value = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "출석 상태 변경 성공"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "출석 기록을 찾을 수 없음") }) - @PutMapping("/status") - public ApiResponse updateAttendanceStatus( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "출석 상태 변경 요청", - required = true, - content = @Content(schema = @Schema(implementation = UpdateAttendanceStatusReq.class)) - ) + @PutMapping("/admin/users/{userId}/attendance/{attendanceId}/status") + public boolean updateAttendanceStatus( + @Parameter(description = "사용자 ID", example = "1") + @PathVariable Long userId, + @Parameter(description = "출석 ID", example = "1") + @PathVariable Long attendanceId, @RequestBody UpdateAttendanceStatusReq req) { - boolean result = attendanceService.updateAttendanceStatus( - req.getAttendanceId(), - req.isStatus() - ); + // userId 파라미터 검증은 여기서 할 수 있음 (필요 시) + return attendanceService.updateAttendanceStatus(attendanceId, req.isStatus()); + } + + // 출석 기록 삭제 (관리자 전용) + @Operation(summary = "출석 기록 삭제", description = "관리자가 특정 사용자의 출석 기록을 삭제합니다.") + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "출석 기록 삭제 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "출석 기록을 찾을 수 없음") + }) + @DeleteMapping("/admin/users/{userId}/attendance/{attendanceId}") + public boolean deleteAttendance( + @Parameter(description = "사용자 ID", example = "1") + @PathVariable Long userId, + @Parameter(description = "출석 ID", example = "1") + @PathVariable Long attendanceId) { - if (result) { - return ApiResponse.success("출석 상태가 성공적으로 변경되었습니다", null); - } else { - return ApiResponse.error("출석 상태 변경에 실패했습니다. 출석 기록을 찾을 수 없습니다."); - } + // userId 파라미터 검증은 여기서 할 수 있음 (필요 시) + return attendanceService.deleteAttendance(attendanceId); } // 특정 날짜와 차수에 대한 모든 학생의 출석 현황 조회 @Operation(summary = "특정 날짜와 차수의 출석 현황 조회", description = "특정 날짜와 차수에 대한 모든 학생의 출석 현황을 조회합니다.") - @ApiResponses({ + @ApiResponses(value = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청") }) - @GetMapping("/list") - public ApiResponse> getAllAttendanceByDateAndOrder( - @Parameter(description = "조회할 날짜 (YYYY-MM-DD)", required = true) + @GetMapping("/admin/attendance/list") + public List getAllAttendanceByDateAndOrder( + @Parameter(description = "조회할 날짜 (YYYY-MM-DD)", example = "2023-08-01") + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, + @Parameter(description = "조회할 차수", example = "1") + @RequestParam int order) { + return attendanceService.findAllByDateAndOrder(date, order); + } + + // 특정 사용자의 특정 날짜와 차수 출석 기록 조회 + @Operation(summary = "특정 사용자의 특정 날짜와 차수 출석 조회", description = "특정 사용자의 특정 날짜와 차수 출석 기록을 조회합니다.") + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "출석 기록을 찾을 수 없음") + }) + @GetMapping("/admin/users/{userId}/attendance") + public UserAttendanceStatusRes getUserAttendanceByDateAndOrder( + @Parameter(description = "사용자 ID", example = "1") + @PathVariable Long userId, + @Parameter(description = "조회할 날짜 (YYYY-MM-DD)", example = "2023-08-01") @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, - @Parameter(description = "조회할 차수", required = true) + @Parameter(description = "조회할 차수", example = "1") @RequestParam int order) { + return attendanceService.findByUserIdAndDateAndOrder(userId, date, order); + } + + // 특정 출석 ID로 출석 기록 조회 + @Operation(summary = "특정 출석 기록 조회", description = "특정 학생의 특정 출석 기록을 ID로 조회합니다.") + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "출석 기록을 찾을 수 없음") + }) + @GetMapping("/admin/users/{userId}/attendance/{attendanceId}") + public UserAttendanceStatusRes getAttendanceById( + @Parameter(description = "사용자 ID", example = "1") + @PathVariable Long userId, + @Parameter(description = "출석 ID", example = "1") + @PathVariable Long attendanceId) { - List attendances = attendanceService.findAllByDateAndOrder(date, order); - return ApiResponse.success(attendances); + UserAttendanceStatusRes attendance = attendanceService.findById(attendanceId); + + if (attendance == null) { + throw new RuntimeException("출석 기록을 찾을 수 없습니다"); + } + + // 요청된 userId와 조회된 출석 기록의 userId가 일치하는지 확인 + if (!attendance.getUserId().equals(userId)) { + throw new RuntimeException("요청된 사용자 ID와 출석 기록의 사용자 ID가 일치하지 않습니다"); + } + + return attendance; + } + + // 학생용 출석 현황 조회 + @Operation(summary = "학생별 출석 현황 조회", description = "특정 학생의 출석 현황을 조회합니다.") + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청") + }) + @GetMapping("/attendance/{userId}") + public List getUserAttendances( + @Parameter(description = "사용자 ID", example = "1") + @PathVariable Long userId) { + return attendanceService.findAllByUserId(userId); } } \ No newline at end of file diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/request/UpdateAttendanceStatusReq.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/request/UpdateAttendanceStatusReq.java index 2c6973c..0bea7c5 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/request/UpdateAttendanceStatusReq.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/request/UpdateAttendanceStatusReq.java @@ -12,9 +12,6 @@ @AllArgsConstructor @Schema(description = "출석 상태 수정 요청") public class UpdateAttendanceStatusReq { - @Schema(description = "출석 기록 ID", example = "1") - private Long attendanceId; - @Schema(description = "변경할 출석 상태", example = "true") private boolean status; } \ No newline at end of file diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/service/AttendanceService.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/service/AttendanceService.java index 4a1adb8..ae0dec4 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/service/AttendanceService.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/service/AttendanceService.java @@ -247,4 +247,82 @@ public List findAllByDateAndOrder(LocalDate date, int o .sorted(Comparator.comparing(UserAttendanceStatusRes::getUsername)) .toList(); } + + // 특정 학생의 모든 출석 현황 조회 + public List findAllByUserId(Long userId) { + // 해당 사용자의 모든 출석 기록 조회 + List attendances = attendanceRepository.findByUserId(userId); + + // DTO 변환 + return attendances.stream() + .map(attendance -> { + User user = attendance.getUser(); + return UserAttendanceStatusRes.builder() + .userId(user.getId()) + .username(user.getName()) + .date(attendance.getDate()) + .order(attendance.getOrder()) + .status(attendance.isStatus()) + .attendanceId(attendance.getId()) + .build(); + }) + .sorted(Comparator.comparing(UserAttendanceStatusRes::getDate).reversed() + .thenComparing(UserAttendanceStatusRes::getOrder)) + .toList(); + } + + // 특정 사용자의 특정 출석 기록 삭제 + @Transactional + public boolean deleteAttendance(Long attendanceId) { + Optional attendanceOpt = attendanceRepository.findById(attendanceId); + + if (attendanceOpt.isEmpty()) { + return false; + } + + attendanceRepository.delete(attendanceOpt.get()); + return true; + } + + // 특정 사용자의 특정 날짜와 차수 출석 기록 조회 + public UserAttendanceStatusRes findByUserIdAndDateAndOrder(Long userId, LocalDate date, int order) { + Optional attendanceOpt = attendanceRepository.findByUserIdAndDateAndOrder(userId, date, order); + + if (attendanceOpt.isEmpty()) { + return null; + } + + Attendance attendance = attendanceOpt.get(); + User user = attendance.getUser(); + + return UserAttendanceStatusRes.builder() + .userId(user.getId()) + .username(user.getName()) + .date(attendance.getDate()) + .order(attendance.getOrder()) + .status(attendance.isStatus()) + .attendanceId(attendance.getId()) + .build(); + } + + // 특정 출석 ID로 출석 기록 조회 + public UserAttendanceStatusRes findById(Long attendanceId) { + Optional attendanceOpt = attendanceRepository.findById(attendanceId); + + if (attendanceOpt.isEmpty()) { + return null; + } + + Attendance attendance = attendanceOpt.get(); + User user = attendance.getUser(); + + return UserAttendanceStatusRes.builder() + .userId(user.getId()) + .username(user.getName()) + .date(attendance.getDate()) + .order(attendance.getOrder()) + .status(attendance.isStatus()) + .attendanceId(attendance.getId()) + .build(); + } }