diff --git a/src/main/java/ybe/mini/travelserver/domain/reservation/exception/ReservationErrorMessage.java b/src/main/java/ybe/mini/travelserver/domain/reservation/exception/ReservationErrorMessage.java index 5fb25a2..d4ea5bb 100644 --- a/src/main/java/ybe/mini/travelserver/domain/reservation/exception/ReservationErrorMessage.java +++ b/src/main/java/ybe/mini/travelserver/domain/reservation/exception/ReservationErrorMessage.java @@ -11,7 +11,8 @@ @AllArgsConstructor public enum ReservationErrorMessage implements ErrorMessage { - RESERVATION_NOT_FOUND(BAD_REQUEST, "해당 ID의 예약 정보가 없습니다.") + RESERVATION_NOT_FOUND(BAD_REQUEST, "해당 ID의 예약 정보가 없습니다."), + ROOM_STOCK_IS_EMPTY(BAD_REQUEST, "예약하려는 객실 상품이 이미 품절되었습니다.") ; private final HttpStatus status; private final String message; diff --git a/src/main/java/ybe/mini/travelserver/domain/reservation/exception/ReservationExceptionHandler.java b/src/main/java/ybe/mini/travelserver/domain/reservation/exception/ReservationExceptionHandler.java index c95ad9f..a20ff97 100644 --- a/src/main/java/ybe/mini/travelserver/domain/reservation/exception/ReservationExceptionHandler.java +++ b/src/main/java/ybe/mini/travelserver/domain/reservation/exception/ReservationExceptionHandler.java @@ -7,6 +7,7 @@ import ybe.mini.travelserver.global.exception.ProblemDetailCreator; import static ybe.mini.travelserver.domain.reservation.exception.ReservationErrorMessage.RESERVATION_NOT_FOUND; +import static ybe.mini.travelserver.domain.reservation.exception.ReservationErrorMessage.ROOM_STOCK_IS_EMPTY; @RestControllerAdvice public class ReservationExceptionHandler extends ProblemDetailCreator { @@ -20,5 +21,10 @@ public ProblemDetail handleReservationNotFoundException(HttpServletRequest reque return createProblemDetail(RESERVATION_NOT_FOUND, request); } + @ExceptionHandler(RoomStockIsEmptyException.class) + public ProblemDetail handleRoomStockIsEmptyException(HttpServletRequest request) { + return createProblemDetail(ROOM_STOCK_IS_EMPTY, request); + } + } diff --git a/src/main/java/ybe/mini/travelserver/domain/reservation/exception/RoomStockIsEmptyException.java b/src/main/java/ybe/mini/travelserver/domain/reservation/exception/RoomStockIsEmptyException.java new file mode 100644 index 0000000..7b87847 --- /dev/null +++ b/src/main/java/ybe/mini/travelserver/domain/reservation/exception/RoomStockIsEmptyException.java @@ -0,0 +1,4 @@ +package ybe.mini.travelserver.domain.reservation.exception; + +public class RoomStockIsEmptyException extends RuntimeException { +} diff --git a/src/main/java/ybe/mini/travelserver/domain/reservation/service/ReservationService.java b/src/main/java/ybe/mini/travelserver/domain/reservation/service/ReservationService.java index bf785bf..cf8c79b 100644 --- a/src/main/java/ybe/mini/travelserver/domain/reservation/service/ReservationService.java +++ b/src/main/java/ybe/mini/travelserver/domain/reservation/service/ReservationService.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cglib.core.Local; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ybe.mini.travelserver.domain.accommodation.entity.Accommodation; @@ -16,14 +17,16 @@ import ybe.mini.travelserver.domain.reservation.dto.ReservationGetResponse; import ybe.mini.travelserver.domain.reservation.entity.Reservation; import ybe.mini.travelserver.domain.reservation.exception.ReservationNotFoundException; +import ybe.mini.travelserver.domain.reservation.exception.RoomStockIsEmptyException; import ybe.mini.travelserver.domain.reservation.repository.ReservationRepository; import ybe.mini.travelserver.domain.reservation_room.dto.ReservationRoomCreateRequest; import ybe.mini.travelserver.domain.reservation_room.entity.ReservationRoom; +import ybe.mini.travelserver.domain.reservation_room.repository.ReservationRoomRepository; import ybe.mini.travelserver.domain.room.entity.Room; import ybe.mini.travelserver.domain.room.repository.RoomRepository; import ybe.mini.travelserver.global.api.TourAPIService; -import ybe.mini.travelserver.global.util.Validation; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @@ -35,6 +38,7 @@ public class ReservationService { private final ReservationRepository reservationRepository; + private final ReservationRoomRepository reservationRoomRepository; private final MemberRepository memberRepository; private final TourAPIService tourAPIService; private final RoomRepository roomRepository; @@ -44,8 +48,12 @@ public class ReservationService { @Transactional public ReservationCreateResponse createReservation(String userEmail, ReservationCreateRequest reservationRequest) { + List reservationRooms = reservationRoomDtosToEntityList(reservationRequest.reservationRooms()); + + reservationRooms.forEach(this::isEnableReservation); + Member member = getMemberByEmail(userEmail); Reservation reservation = Reservation.createReservation(member, reservationRequest.paymentType(), reservationRooms); @@ -53,6 +61,22 @@ public ReservationCreateResponse createReservation(String userEmail, Reservation return ReservationCreateResponse.fromEntity(reservationRepository.save(reservation)); } + private void isEnableReservation(ReservationRoom resRoom) { + int restStock = getRestStock( + resRoom.getRoom(), resRoom.getCheckIn(), resRoom.getCheckOut() + ); + if(restStock==0) throw new RoomStockIsEmptyException(); + } + + private Integer getRestStock(Room room, LocalDate checkIn, LocalDate checkOut) { + List reservationRooms = + reservationRoomRepository.findAllByRoomAndCheckInBetweenAndCheckOutBetween( + room, checkIn, checkOut, checkIn, checkOut + ); + return Math.max(0, room.getStock() - reservationRooms.size()); + + } + public ReservationCreateResponse createReservationAndDeleteCart( String userEmail, ReservationCreateFromCartRequest reservationRequest ) { @@ -95,7 +119,7 @@ private ReservationRoom reservationRoomDtoToEntity(ReservationRoomCreateRequest String areaCodeString = (roomRequest.areaCode() != null) ? String.valueOf(roomRequest.areaCode().getCode()) : null; - Accommodation accommodation = //todo : Bring 제대로 안되면 Exception 날려주는 거 고민 + Accommodation accommodation = tourAPIService.bringAccommodation(roomRequest.accommodationName(), areaCodeString); Room room = tourAPIService.bringRoom(roomRequest.accommodationId(), roomRequest.roomTypeId()); diff --git a/src/main/java/ybe/mini/travelserver/domain/reservation_room/dto/ReservationRoomCreateRequest.java b/src/main/java/ybe/mini/travelserver/domain/reservation_room/dto/ReservationRoomCreateRequest.java index 432fbcc..bef8249 100644 --- a/src/main/java/ybe/mini/travelserver/domain/reservation_room/dto/ReservationRoomCreateRequest.java +++ b/src/main/java/ybe/mini/travelserver/domain/reservation_room/dto/ReservationRoomCreateRequest.java @@ -1,12 +1,13 @@ package ybe.mini.travelserver.domain.reservation_room.dto; -import jakarta.validation.constraints.*; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import lombok.Builder; -import org.springframework.format.annotation.DateTimeFormat; import ybe.mini.travelserver.domain.accommodation.entity.AreaCode; import java.io.Serializable; -import java.time.LocalDateTime; @Builder public record ReservationRoomCreateRequest( diff --git a/src/main/java/ybe/mini/travelserver/domain/room/service/RoomService.java b/src/main/java/ybe/mini/travelserver/domain/room/service/RoomService.java index 517de79..41773b6 100644 --- a/src/main/java/ybe/mini/travelserver/domain/room/service/RoomService.java +++ b/src/main/java/ybe/mini/travelserver/domain/room/service/RoomService.java @@ -44,7 +44,7 @@ private Integer getRestStock(Room room, LocalDate checkIn, LocalDate checkOut) { reservationRoomRepository.findAllByRoomAndCheckInBetweenAndCheckOutBetween( roomOpt.get(), checkIn, checkOut, checkIn, checkOut ); - return room.getStock() - reservationRooms.size(); + return Math.max(0, room.getStock() - reservationRooms.size()); } } diff --git a/src/test/http/reservation.http b/src/test/http/reservation.http index 987051c..48d4205 100644 --- a/src/test/http/reservation.http +++ b/src/test/http/reservation.http @@ -1,7 +1,7 @@ -### 예약 생성 +### 예약 생성1 POST http://localhost:8080/reservations Content-Type: application/json -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiaWF0IjoxNzAxMTU2NTc2LCJleHAiOjE3MDExNTgzNzYsImVtYWlsIjoib2tqYWVvb2s5OEBnbWFpbC5jb20iLCJwYXNzd29yZCI6IntiY3J5cHR9JDJhJDEwJFNCbjZSQ3ZQdFQ2WW1vSVM0ZzU1dE8xTFBwRDJkSjJLbDBKcDRXT3k5RmhuNzNxdnNJQS51IiwibmFtZSI6Im9ramFlb29rIn0.HCiMHNhtW45GEFH2tbkihaeiqADBmehLfg2iGlIDuCE +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiaWF0IjoxNzAxMjQ1MTA0LCJleHAiOjE3MDEyNDY5MDQsImVtYWlsIjoib2tqYWVvb2s5OEBnbWFpbC5jb20iLCJwYXNzd29yZCI6IntiY3J5cHR9JDJhJDEwJFNCbjZSQ3ZQdFQ2WW1vSVM0ZzU1dE8xTFBwRDJkSjJLbDBKcDRXT3k5RmhuNzNxdnNJQS51IiwibmFtZSI6Im9ramFlb29rIn0.cqTN4G92c9KyiwW-0XCFRBM-SUDzIsEyxQFqJnVwzYI { "paymentType": "CARD", @@ -11,8 +11,28 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiaWF0I "accommodationName": "가락관광호텔", "areaCode": "SEOUL", "roomTypeId": 11430, - "checkIn": "2024-01-01T00:00", - "checkOut": "2024-01-03T00:00", + "checkIn": "20250101", + "checkOut": "20250103", + "guestNumber": 2 + } + ] +} + +### 예약 생성2 +POST http://localhost:8080/reservations +Content-Type: application/json +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiaWF0IjoxNzAxMjQ1MTA0LCJleHAiOjE3MDEyNDY5MDQsImVtYWlsIjoib2tqYWVvb2s5OEBnbWFpbC5jb20iLCJwYXNzd29yZCI6IntiY3J5cHR9JDJhJDEwJFNCbjZSQ3ZQdFQ2WW1vSVM0ZzU1dE8xTFBwRDJkSjJLbDBKcDRXT3k5RmhuNzNxdnNJQS51IiwibmFtZSI6Im9ramFlb29rIn0.cqTN4G92c9KyiwW-0XCFRBM-SUDzIsEyxQFqJnVwzYI + +{ + "paymentType": "CARD", + "reservationRooms" : [ + { + "accommodationId": 819849, + "accommodationName": "킹모텔(구 호텔킹)", + "areaCode": "GYEONGBUK", + "roomTypeId": 823, + "checkIn": "20250101", + "checkOut": "20250103", "guestNumber": 2 } ] diff --git a/src/test/http/token.http b/src/test/http/token.http index d149b4b..a87bb3e 100644 --- a/src/test/http/token.http +++ b/src/test/http/token.http @@ -19,7 +19,7 @@ Content-Type: application/json ### 회원 마이페이지 올바른 토큰 GET http://localhost:8080/members/mypage -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1IiwiaWF0IjoxNzAwOTg2ODIwLCJleHAiOjE3MDA5ODg2MjAsImVtYWlsIjoib2tqYWVvb2s5OEBnbWFpbC5jb20ifQ.t6iD-_ymvp5jVKuihpndMDC4Zn6yc8wlTlil-DAci1E +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiaWF0IjoxNzAxMjQ1MTA0LCJleHAiOjE3MDEyNDY5MDQsImVtYWlsIjoib2tqYWVvb2s5OEBnbWFpbC5jb20iLCJwYXNzd29yZCI6IntiY3J5cHR9JDJhJDEwJFNCbjZSQ3ZQdFQ2WW1vSVM0ZzU1dE8xTFBwRDJkSjJLbDBKcDRXT3k5RmhuNzNxdnNJQS51IiwibmFtZSI6Im9ramFlb29rIn0.cqTN4G92c9KyiwW-0XCFRBM-SUDzIsEyxQFqJnVwzYI #### 만료된 토큰 GET http://localhost:8080/members/mypage diff --git a/src/test/java/ybe/mini/travelserver/domain/reservation/service/ReservationServiceTest.java b/src/test/java/ybe/mini/travelserver/domain/reservation/service/ReservationServiceTest.java index daee7a3..2dc66af 100644 --- a/src/test/java/ybe/mini/travelserver/domain/reservation/service/ReservationServiceTest.java +++ b/src/test/java/ybe/mini/travelserver/domain/reservation/service/ReservationServiceTest.java @@ -12,6 +12,7 @@ import ybe.mini.travelserver.domain.reservation.dummy.DummyReservationDTO; import ybe.mini.travelserver.domain.reservation.entity.Reservation; import ybe.mini.travelserver.domain.reservation.repository.ReservationRepository; +import ybe.mini.travelserver.domain.reservation_room.repository.ReservationRoomRepository; import ybe.mini.travelserver.domain.room.DummyObjectForRoom; import ybe.mini.travelserver.domain.room.entity.Room; import ybe.mini.travelserver.domain.room.repository.RoomRepository; @@ -37,6 +38,8 @@ class ReservationServiceTest implements DummyObjectForRoom, DummyReservationDTO, RoomRepository roomRepository; @Mock AccommodationRepository accommodationRepository; + @Mock + ReservationRoomRepository reservationRoomRepository; @InjectMocks ReservationService reservationService;