diff --git a/src/main/kotlin/roomescape/reservation/business/ReservationService.kt b/src/main/kotlin/roomescape/reservation/business/ReservationService.kt index f722db2b..fc67d4cd 100644 --- a/src/main/kotlin/roomescape/reservation/business/ReservationService.kt +++ b/src/main/kotlin/roomescape/reservation/business/ReservationService.kt @@ -21,7 +21,7 @@ import roomescape.schedule.infrastructure.persistence.ScheduleStatus import roomescape.schedule.web.ScheduleSummaryResponse import roomescape.schedule.web.ScheduleUpdateRequest import roomescape.theme.business.ThemeService -import roomescape.theme.web.ThemeRetrieveResponseV2 +import roomescape.theme.web.ThemeSummaryResponse import java.time.LocalDateTime private val log: KLogger = KotlinLogging.logger {} @@ -29,6 +29,7 @@ private val log: KLogger = KotlinLogging.logger {} @Service class ReservationService( private val reservationRepository: ReservationRepository, + private val reservationValidator: ReservationValidator, private val scheduleService: ScheduleService, private val memberService: MemberService, private val themeService: ThemeService, @@ -44,10 +45,9 @@ class ReservationService( ): PendingReservationCreateResponse { log.info { "[ReservationService.createPendingReservation] Pending 예약 생성 시작: schedule=${request.scheduleId}" } - val reservation: ReservationEntity = request.toEntity( - id = tsidFactory.next(), - memberId = memberId - ) + validateCanCreate(request) + + val reservation: ReservationEntity = request.toEntity(id = tsidFactory.next(), memberId = memberId) return PendingReservationCreateResponse(reservationRepository.save(reservation).id) .also { "[ReservationService.createPendingReservation] Pending 예약 생성 완료: reservationId=${it}, schedule=${request.scheduleId}" } @@ -58,10 +58,6 @@ class ReservationService( log.info { "[ReservationService.confirmReservation] Pending 예약 확정 시작: reservationId=${id}" } val reservation: ReservationEntity = findOrThrow(id) - if (reservation.status != ReservationStatus.PENDING) { - log.warn { "[ReservationService.confirmReservation] 예약이 Pending 상태가 아님: reservationId=${id}, status=${reservation.status}" } - } - run { reservation.confirm() scheduleService.updateSchedule( @@ -100,7 +96,7 @@ class ReservationService( return ReservationSummaryRetrieveListResponse(reservations.map { val schedule: ScheduleSummaryResponse = scheduleService.findSummaryById(it.scheduleId) - val theme: ThemeRetrieveResponseV2 = themeService.findById(schedule.themeId) + val theme: ThemeSummaryResponse = themeService.findSummaryById(schedule.themeId) ReservationSummaryRetrieveResponse( id = it.id, @@ -109,7 +105,9 @@ class ReservationService( startAt = schedule.time, status = it.status ) - }) + }).also { + log.info { "[ReservationService.findSummaryByMemberId] ${it.reservations.size}개의 예약 조회 완료: memberId=${memberId}" } + } } @Transactional(readOnly = true) @@ -160,4 +158,11 @@ class ReservationService( canceledReservationRepository.save(it) } } + + private fun validateCanCreate(request: PendingReservationCreateRequest) { + val schedule = scheduleService.findSummaryById(request.scheduleId) + val theme = themeService.findSummaryById(schedule.themeId) + + reservationValidator.validateCanCreate(schedule, theme, request) + } } diff --git a/src/main/kotlin/roomescape/reservation/business/ReservationValidator.kt b/src/main/kotlin/roomescape/reservation/business/ReservationValidator.kt new file mode 100644 index 00000000..2bcdaea8 --- /dev/null +++ b/src/main/kotlin/roomescape/reservation/business/ReservationValidator.kt @@ -0,0 +1,38 @@ +package roomescape.reservation.business + +import io.github.oshai.kotlinlogging.KLogger +import io.github.oshai.kotlinlogging.KotlinLogging +import org.springframework.stereotype.Component +import roomescape.reservation.exception.ReservationErrorCode +import roomescape.reservation.exception.ReservationException +import roomescape.reservation.web.PendingReservationCreateRequest +import roomescape.schedule.infrastructure.persistence.ScheduleStatus +import roomescape.schedule.web.ScheduleSummaryResponse +import roomescape.theme.web.ThemeSummaryResponse + +private val log: KLogger = KotlinLogging.logger {} + +@Component +class ReservationValidator { + + fun validateCanCreate( + schedule: ScheduleSummaryResponse, + theme: ThemeSummaryResponse, + request: PendingReservationCreateRequest + ) { + if (schedule.status != ScheduleStatus.HOLD) { + log.warn { "[ReservationValidator.validateCanCreate] ${schedule.status}인 일정으로 인한 예약 실패" } + throw ReservationException(ReservationErrorCode.SCHEDULE_NOT_HOLD) + } + + if (theme.minParticipants > request.participantCount) { + log.info { "[ReservationValidator.validateCanCreate] 최소 인원 미달로 인한 예약 실패: minParticipants=${theme.minParticipants}, participantCount=${request.participantCount}" } + throw ReservationException(ReservationErrorCode.INVALID_PARTICIPANT_COUNT) + } + + if (theme.maxParticipants < request.participantCount) { + log.info { "[ReservationValidator.validateCanCreate] 최대 인원 초과로 인한 예약 실패: minParticipants=${theme.minParticipants}, participantCount=${request.participantCount}" } + throw ReservationException(ReservationErrorCode.INVALID_PARTICIPANT_COUNT) + } + } +} diff --git a/src/main/kotlin/roomescape/reservation/exception/ReservationErrorCode.kt b/src/main/kotlin/roomescape/reservation/exception/ReservationErrorCode.kt index 33cc6c2d..942b9984 100644 --- a/src/main/kotlin/roomescape/reservation/exception/ReservationErrorCode.kt +++ b/src/main/kotlin/roomescape/reservation/exception/ReservationErrorCode.kt @@ -11,5 +11,7 @@ enum class ReservationErrorCode( RESERVATION_NOT_FOUND(HttpStatus.NOT_FOUND, "R001", "예약을 찾을 수 없어요."), NO_PERMISSION_TO_CANCEL_RESERVATION(HttpStatus.FORBIDDEN, "R002", "예약을 취소할 수 있는 권한이 없어요."), INVALID_SEARCH_DATE_RANGE(HttpStatus.BAD_REQUEST, "R003", "종료 날짜는 시작 날짜 이후여야 해요."), + SCHEDULE_NOT_HOLD(HttpStatus.BAD_REQUEST, "R004", "이미 예약되었거나 예약이 불가능한 일정이에요."), + INVALID_PARTICIPANT_COUNT(HttpStatus.BAD_REQUEST, "R005", "참여 가능 인원 수를 확인해주세요.") ; } diff --git a/src/main/kotlin/roomescape/reservation/web/ReservationController.kt b/src/main/kotlin/roomescape/reservation/web/ReservationController.kt index f4317e5e..b5e60372 100644 --- a/src/main/kotlin/roomescape/reservation/web/ReservationController.kt +++ b/src/main/kotlin/roomescape/reservation/web/ReservationController.kt @@ -24,7 +24,7 @@ class ReservationController( return ResponseEntity.ok(CommonApiResponse(response)) } - @PatchMapping("/reservations/{id}/confirm") + @PostMapping("/reservations/{id}/confirm") override fun confirmReservation( @PathVariable("id") id: Long ): ResponseEntity> { @@ -41,7 +41,7 @@ class ReservationController( ): ResponseEntity> { reservationService.cancelReservation(memberId, reservationId, request) - return ResponseEntity.noContent().build() + return ResponseEntity.ok().body(CommonApiResponse()) } @GetMapping("/reservations/summary") diff --git a/src/main/kotlin/roomescape/reservation/web/ReservationDto.kt b/src/main/kotlin/roomescape/reservation/web/ReservationDto.kt index 85ee01fa..43352f3e 100644 --- a/src/main/kotlin/roomescape/reservation/web/ReservationDto.kt +++ b/src/main/kotlin/roomescape/reservation/web/ReservationDto.kt @@ -1,5 +1,6 @@ package roomescape.reservation.web +import jakarta.validation.constraints.NotEmpty import roomescape.member.web.MemberSummaryRetrieveResponse import roomescape.payment.web.PaymentRetrieveResponse import roomescape.reservation.infrastructure.persistence.ReservationEntity @@ -10,7 +11,9 @@ import java.time.LocalTime data class PendingReservationCreateRequest( val scheduleId: Long, + @NotEmpty val reserverName: String, + @NotEmpty val reserverContact: String, val participantCount: Short, val requirement: String