diff --git a/src/main/java/com/groomiz/billage/classroom/dto/AdminReservationSearchByClassroomCond.java b/src/main/java/com/groomiz/billage/classroom/dto/AdminReservationSearchByClassroomCond.java new file mode 100644 index 0000000..0374521 --- /dev/null +++ b/src/main/java/com/groomiz/billage/classroom/dto/AdminReservationSearchByClassroomCond.java @@ -0,0 +1,24 @@ +package com.groomiz.billage.classroom.dto; + +import com.groomiz.billage.classroom.entity.Classroom; +import com.groomiz.billage.global.dto.PageRequestDto; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class AdminReservationSearchByClassroomCond extends PageRequestDto { + + private Classroom classroom; + private LocalDate applyDate; + + @Builder + public AdminReservationSearchByClassroomCond(Classroom classroom, LocalDate applyDate) { + this.classroom = classroom; + this.applyDate = applyDate; + } +} diff --git a/src/main/java/com/groomiz/billage/classroom/dto/response/AdminClassroomDetailResponse.java b/src/main/java/com/groomiz/billage/classroom/dto/response/AdminClassroomDetailResponse.java index 7c08db1..dda8371 100644 --- a/src/main/java/com/groomiz/billage/classroom/dto/response/AdminClassroomDetailResponse.java +++ b/src/main/java/com/groomiz/billage/classroom/dto/response/AdminClassroomDetailResponse.java @@ -4,6 +4,7 @@ import java.time.LocalTime; import java.util.List; +import com.fasterxml.jackson.annotation.JsonFormat; import com.groomiz.billage.classroom.entity.Classroom; import com.groomiz.billage.member.entity.Member; import com.groomiz.billage.reservation.entity.Reservation; @@ -36,7 +37,7 @@ public class AdminClassroomDetailResponse { private List reservations; @Data - @NoArgsConstructor(access = AccessLevel.PROTECTED) + @NoArgsConstructor @Schema(description = "예약 상세 정보") public static class ReservationDetail { @@ -49,10 +50,12 @@ public static class ReservationDetail { @Schema(description = "예약 날짜", example = "2024-09-04") private LocalDate date; - @Schema(description = "시작 시간", example = "09:00") + @Schema(description = "시작 시간", example = "09:00", type = "string") + @JsonFormat(pattern = "HH:mm") private LocalTime startTime; - @Schema(description = "종료 시간", example = "10:00") + @Schema(description = "종료 시간", example = "10:00", type = "string") + @JsonFormat(pattern = "HH:mm") private LocalTime endTime; @Schema(description = "인원 수", example = "10") @@ -63,35 +66,6 @@ public static class ReservationDetail { @Schema(description = "예약자 학번", example = "20201234") private String studentNumber; - - @Builder - public ReservationDetail(Long reservationId, ReservationStatusType status, LocalDate date, LocalTime startTime, LocalTime endTime, - Integer headcount, - String memberName, String studentNumber) { - this.reservationId = reservationId; - this.status = status; - this.date = date; - this.startTime = startTime; - this.endTime = endTime; - this.headcount = headcount; - this.memberName = memberName; - this.studentNumber = studentNumber; - } - - public static ReservationDetail from(Reservation reservation) { - Member requester = reservation.getReservationStatus().getRequester(); - - return ReservationDetail.builder() - .reservationId(reservation.getId()) - .status(reservation.getReservationStatus().getStatus()) - .date(reservation.getApplyDate()) - .startTime(reservation.getStartTime()) - .endTime(reservation.getEndTime()) - .headcount(reservation.getHeadcount()) - .memberName(requester.getUsername()) - .studentNumber(requester.getStudentNumber()) - .build(); - } } @Builder @@ -104,18 +78,13 @@ public AdminClassroomDetailResponse(LocalDate date, String buildingName, Long fl this.reservations = reservations; } - public static AdminClassroomDetailResponse from(Classroom classroom, LocalDate date) { - List reservations = classroom.getReservations(); - - + public static AdminClassroomDetailResponse from(Classroom classroom, List reservations, LocalDate date) { return AdminClassroomDetailResponse.builder() .date(date) .buildingName(classroom.getBuilding().getName()) .floor(classroom.getFloor()) .classroomName(classroom.getName()) - .reservations(reservations.stream() - .map(ReservationDetail::from) - .toList()) + .reservations(reservations) .build(); } } diff --git a/src/main/java/com/groomiz/billage/classroom/repository/ClassroomRepository.java b/src/main/java/com/groomiz/billage/classroom/repository/ClassroomRepository.java index 4911103..a5158e0 100644 --- a/src/main/java/com/groomiz/billage/classroom/repository/ClassroomRepository.java +++ b/src/main/java/com/groomiz/billage/classroom/repository/ClassroomRepository.java @@ -12,15 +12,6 @@ import com.groomiz.billage.classroom.entity.Classroom; public interface ClassroomRepository extends JpaRepository { - - @Query("SELECT c FROM Classroom c " - + "JOIN FETCH c.reservations r " - + "JOIN FETCH r.reservationStatus rs " - + "WHERE c.id = :classroomId " - + "AND r.applyDate = :date " - + "AND rs.status in ('PENDING', 'APPROVED')") - Optional findClassroomByIdAndDate(Long classroomId, LocalDate date); - Optional findClassroomById(Long classroomId); List findClassroomByBuildingIdAndFloorAndCapacityGreaterThanEqual(Long buildingId, Long floor, Integer capacity); diff --git a/src/main/java/com/groomiz/billage/classroom/service/AdminClassroomService.java b/src/main/java/com/groomiz/billage/classroom/service/AdminClassroomService.java index 7232d89..0a8172c 100644 --- a/src/main/java/com/groomiz/billage/classroom/service/AdminClassroomService.java +++ b/src/main/java/com/groomiz/billage/classroom/service/AdminClassroomService.java @@ -4,6 +4,8 @@ import java.util.List; import java.util.stream.Collectors; +import com.groomiz.billage.classroom.dto.AdminReservationSearchByClassroomCond; +import com.groomiz.billage.classroom.dto.response.AdminClassroomDetailResponse.ReservationDetail; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -33,10 +35,17 @@ public class AdminClassroomService { @Transactional(readOnly = true) public AdminClassroomDetailResponse findClassroomByIdAndDate(Long classroomId, LocalDate date) { - Classroom classroom = classroomRepository.findClassroomByIdAndDate(classroomId, date) - .orElseThrow(() -> new ClassroomException(ClassroomErrorCode.CLASSROOM_NOT_FOUND)); + Classroom classroom = classroomRepository.findClassroomById(classroomId) + .orElseThrow(() -> new ClassroomException(ClassroomErrorCode.CLASSROOM_NOT_FOUND)); - return AdminClassroomDetailResponse.from(classroom, date); + AdminReservationSearchByClassroomCond cond = AdminReservationSearchByClassroomCond.builder() + .classroom(classroom) + .applyDate(date) + .build(); + + List reservations = reservationRepository.searchPendingAndApprovedReservationByClassroom(cond); + + return AdminClassroomDetailResponse.from(classroom, reservations, date); } @Transactional(readOnly = true) diff --git a/src/main/java/com/groomiz/billage/global/exception/GlobalExceptionHandler.java b/src/main/java/com/groomiz/billage/global/exception/GlobalExceptionHandler.java index c26e279..4eea705 100644 --- a/src/main/java/com/groomiz/billage/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/groomiz/billage/global/exception/GlobalExceptionHandler.java @@ -9,6 +9,7 @@ import java.util.Map; import java.util.stream.Collectors; +import com.groomiz.billage.classroom.exception.ClassroomException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; @@ -131,6 +132,18 @@ public ResponseEntity handleAdminReservationException(Reservation .body(errorResponse); } + @ExceptionHandler(ClassroomException.class) + public ResponseEntity handleCLassroomException(ClassroomException ex, HttpServletRequest request) { + + log.info("ClassroomException: {}", ex.getMessage()); + ErrorReason reason = ex.getErrorReason(); + ErrorResponse errorResponse = + new ErrorResponse(ex.getErrorReason(), request.getRequestURL().toString()); + + return ResponseEntity.status(HttpStatus.valueOf(reason.getStatus())) + .body(errorResponse); + } + @ExceptionHandler(AuthException.class) public ResponseEntity handleAuthException(AuthException ex, HttpServletRequest request) { ErrorReason reason = ex.getErrorCode().getErrorReason(); diff --git a/src/main/java/com/groomiz/billage/reservation/repository/ReservationRepositoryCustom.java b/src/main/java/com/groomiz/billage/reservation/repository/ReservationRepositoryCustom.java index 319ae26..b32835f 100644 --- a/src/main/java/com/groomiz/billage/reservation/repository/ReservationRepositoryCustom.java +++ b/src/main/java/com/groomiz/billage/reservation/repository/ReservationRepositoryCustom.java @@ -1,5 +1,7 @@ package com.groomiz.billage.reservation.repository; +import com.groomiz.billage.classroom.dto.response.AdminClassroomDetailResponse.ReservationDetail; +import com.groomiz.billage.classroom.dto.AdminReservationSearchByClassroomCond; import org.springframework.data.domain.Page; import com.groomiz.billage.reservation.dto.AdminReservationSearchCond; @@ -7,6 +9,8 @@ import com.groomiz.billage.reservation.dto.response.AdminReservationStatusListResponse.ReservationInfo; import com.groomiz.billage.reservation.dto.response.ReservationStatusListResponse; +import java.util.List; + public interface ReservationRepositoryCustom { Page searchPendingReservationPageByBuilding(AdminReservationSearchCond adminReservationSearchCond); @@ -15,4 +19,6 @@ Page searchRejectedAndCanceledReservationPageByAdmin( AdminReservationSearchCond adminReservationSearchCond); Page searchUpcomingReservationPageByRequester(ReservationSearchCond reservationSearchCond); Page searchPastReservationPageByRequester(ReservationSearchCond reservationSearchCond); + + List searchPendingAndApprovedReservationByClassroom(AdminReservationSearchByClassroomCond reservationSearchCond); } diff --git a/src/main/java/com/groomiz/billage/reservation/repository/ReservationRepositoryImpl.java b/src/main/java/com/groomiz/billage/reservation/repository/ReservationRepositoryImpl.java index 3c6fa3f..84e08eb 100644 --- a/src/main/java/com/groomiz/billage/reservation/repository/ReservationRepositoryImpl.java +++ b/src/main/java/com/groomiz/billage/reservation/repository/ReservationRepositoryImpl.java @@ -10,6 +10,9 @@ import java.util.Collection; import java.util.List; +import com.groomiz.billage.classroom.dto.response.AdminClassroomDetailResponse.ReservationDetail; +import com.groomiz.billage.classroom.entity.Classroom; +import com.groomiz.billage.classroom.dto.AdminReservationSearchByClassroomCond; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.support.PageableExecutionUtils; @@ -88,6 +91,33 @@ public Page searchPastReservation ); } + @Override + public List searchPendingAndApprovedReservationByClassroom(AdminReservationSearchByClassroomCond reservationSearchCond) { + return queryFactory + .select( + Projections.fields(ReservationDetail.class, + reservation.id.as("reservationId"), + reservationStatus.status, + reservation.applyDate, + reservation.startTime, + reservation.endTime, + reservation.headcount, + member.username.as("memberName"), + member.studentNumber + ) + ).from(reservation) + .join(reservation.reservationStatus, reservationStatus) + .join(reservation.classroom, classroom) + .join(reservationStatus.requester, member) + .where( + statusIn(List.of(ReservationStatusType.APPROVED, ReservationStatusType.PENDING)), + classroomEq(reservationSearchCond.getClassroom()), + applyDateEq(reservationSearchCond.getApplyDate()) + ) + .fetch(); + } + + private Page fetchAdminReservationPage(Pageable pageable, BooleanExpression... conditions) { List content = queryFactory .select( @@ -188,4 +218,13 @@ private BooleanExpression applyDateGoe(LocalDate today) { private BooleanExpression applyDateLt(LocalDate today) { return today != null ? reservation.applyDate.lt(today) : null; } + + private BooleanExpression classroomEq(Classroom classroomCond) { + return classroomCond != null ? classroom.eq(classroomCond) : null; + } + + private BooleanExpression applyDateEq(LocalDate date) { + return date != null ? reservation.applyDate.eq(date) : null; + } + }