refactor: 새로 정의된 커스텀 예외를 Service에 반영

This commit is contained in:
이상진 2025-07-24 09:57:46 +09:00
parent 2d4b67ad98
commit faf6e408b6
4 changed files with 53 additions and 60 deletions

View File

@ -2,18 +2,19 @@ package roomescape.reservation.business
import org.springframework.data.jpa.domain.Specification import org.springframework.data.jpa.domain.Specification
import org.springframework.data.repository.findByIdOrNull import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.annotation.Transactional
import roomescape.common.exception.ErrorType
import roomescape.common.exception.RoomescapeException
import roomescape.member.business.MemberService import roomescape.member.business.MemberService
import roomescape.member.infrastructure.persistence.MemberEntity
import roomescape.reservation.exception.ReservationErrorCode
import roomescape.reservation.exception.ReservationException
import roomescape.reservation.infrastructure.persistence.ReservationEntity import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.reservation.infrastructure.persistence.ReservationRepository import roomescape.reservation.infrastructure.persistence.ReservationRepository
import roomescape.reservation.infrastructure.persistence.ReservationSearchSpecification import roomescape.reservation.infrastructure.persistence.ReservationSearchSpecification
import roomescape.reservation.infrastructure.persistence.ReservationStatus import roomescape.reservation.infrastructure.persistence.ReservationStatus
import roomescape.reservation.web.* import roomescape.reservation.web.*
import roomescape.theme.business.ThemeService import roomescape.theme.business.ThemeService
import roomescape.theme.infrastructure.persistence.ThemeEntity
import roomescape.time.business.TimeService import roomescape.time.business.TimeService
import roomescape.time.infrastructure.persistence.TimeEntity import roomescape.time.infrastructure.persistence.TimeEntity
import java.time.LocalDate import java.time.LocalDate
@ -34,7 +35,6 @@ class ReservationService(
.confirmed() .confirmed()
.build() .build()
return ReservationRetrieveListResponse(findAllReservationByStatus(spec)) return ReservationRetrieveListResponse(findAllReservationByStatus(spec))
} }
@ -56,17 +56,18 @@ class ReservationService(
reservationRepository.deleteById(reservationId) reservationRepository.deleteById(reservationId)
} }
fun addReservation(request: ReservationCreateWithPaymentRequest, memberId: Long): ReservationEntity { fun createConfirmedReservation(
validateIsReservationExist(request.themeId, request.timeId, request.date) request: ReservationCreateWithPaymentRequest,
return getReservationForSave( memberId: Long
request.timeId, ): ReservationEntity {
request.themeId, val themeId = request.themeId
request.date, val timeId = request.timeId
memberId, val date: LocalDate = request.date
ReservationStatus.CONFIRMED validateIsReservationExist(themeId, timeId, date)
).also {
reservationRepository.save(it) val reservation: ReservationEntity = createEntity(timeId, themeId, date, memberId, ReservationStatus.CONFIRMED)
}
return reservationRepository.save(reservation)
} }
fun createReservationByAdmin(request: AdminReservationCreateRequest): ReservationRetrieveResponse { fun createReservationByAdmin(request: AdminReservationCreateRequest): ReservationRetrieveResponse {
@ -98,12 +99,12 @@ class ReservationService(
date: LocalDate, date: LocalDate,
memberId: Long, memberId: Long,
status: ReservationStatus status: ReservationStatus
): ReservationRetrieveResponse = getReservationForSave(timeId, themeId, date, memberId, status) ): ReservationRetrieveResponse = createEntity(timeId, themeId, date, memberId, status)
.also { .also {
reservationRepository.save(it) reservationRepository.save(it)
}.toRetrieveResponse() }.toRetrieveResponse()
private fun validateMemberAlreadyReserve(themeId: Long?, timeId: Long?, date: LocalDate?, memberId: Long?) { private fun validateMemberAlreadyReserve(themeId: Long, timeId: Long, date: LocalDate, memberId: Long) {
val spec: Specification<ReservationEntity> = ReservationSearchSpecification() val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
.sameMemberId(memberId) .sameMemberId(memberId)
.sameThemeId(themeId) .sameThemeId(themeId)
@ -112,7 +113,7 @@ class ReservationService(
.build() .build()
if (reservationRepository.exists(spec)) { if (reservationRepository.exists(spec)) {
throw RoomescapeException(ErrorType.HAS_RESERVATION_OR_WAITING, HttpStatus.BAD_REQUEST) throw ReservationException(ReservationErrorCode.ALREADY_RESERVE)
} }
} }
@ -125,7 +126,7 @@ class ReservationService(
.build() .build()
if (reservationRepository.exists(spec)) { if (reservationRepository.exists(spec)) {
throw RoomescapeException(ErrorType.RESERVATION_DUPLICATED, HttpStatus.CONFLICT) throw ReservationException(ReservationErrorCode.RESERVATION_DUPLICATED)
} }
} }
@ -137,24 +138,20 @@ class ReservationService(
val request = LocalDateTime.of(requestDate, requestTime.startAt) val request = LocalDateTime.of(requestDate, requestTime.startAt)
if (request.isBefore(now)) { if (request.isBefore(now)) {
throw RoomescapeException( throw ReservationException(ReservationErrorCode.PAST_REQUEST_DATETIME)
ErrorType.RESERVATION_PERIOD_IN_PAST,
"[now: $now | request: $request]",
HttpStatus.BAD_REQUEST
)
} }
} }
private fun getReservationForSave( private fun createEntity(
timeId: Long, timeId: Long,
themeId: Long, themeId: Long,
date: LocalDate, date: LocalDate,
memberId: Long, memberId: Long,
status: ReservationStatus status: ReservationStatus
): ReservationEntity { ): ReservationEntity {
val time = timeService.findById(timeId) val time: TimeEntity = timeService.findById(timeId)
val theme = themeService.findById(themeId) val theme: ThemeEntity = themeService.findById(themeId)
val member = memberService.findById(memberId) val member: MemberEntity = memberService.findById(memberId)
validateDateAndTime(date, time) validateDateAndTime(date, time)
@ -191,58 +188,54 @@ class ReservationService(
return return
} }
if (startFrom.isAfter(endAt)) { if (startFrom.isAfter(endAt)) {
throw RoomescapeException( throw ReservationException(ReservationErrorCode.INVALID_SEARCH_DATE_RANGE)
ErrorType.INVALID_DATE_RANGE,
"[startFrom: $startFrom, endAt: $endAt", HttpStatus.BAD_REQUEST
)
} }
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
fun findReservationsByMemberId(memberId: Long): MyReservationRetrieveListResponse { fun findReservationsByMemberId(memberId: Long): MyReservationRetrieveListResponse {
return MyReservationRetrieveListResponse(reservationRepository.findAllById(memberId)) return MyReservationRetrieveListResponse(reservationRepository.findAllByMemberId(memberId))
} }
fun confirmWaiting(reservationId: Long, memberId: Long) { fun confirmWaiting(reservationId: Long, memberId: Long) {
validateIsMemberAdmin(memberId) validateIsMemberAdmin(memberId)
if (reservationRepository.isExistConfirmedReservation(reservationId)) { if (reservationRepository.isExistConfirmedReservation(reservationId)) {
throw RoomescapeException(ErrorType.RESERVATION_DUPLICATED, HttpStatus.CONFLICT) throw ReservationException(ReservationErrorCode.CONFIRMED_RESERVATION_ALREADY_EXISTS)
} }
reservationRepository.updateStatusByReservationId(reservationId, ReservationStatus.CONFIRMED_PAYMENT_REQUIRED) reservationRepository.updateStatusByReservationId(reservationId, ReservationStatus.CONFIRMED_PAYMENT_REQUIRED)
} }
fun deleteWaiting(reservationId: Long, memberId: Long) { fun deleteWaiting(reservationId: Long, memberId: Long) {
reservationRepository.findByIdOrNull(reservationId)?.takeIf { val reservation: ReservationEntity = findReservationOrThrow(reservationId)
it.isWaiting() && it.isSameMember(memberId) if (!reservation.isWaiting()) {
}?.let { throw ReservationException(ReservationErrorCode.ALREADY_CONFIRMED)
reservationRepository.delete(it) }
} ?: throw throwReservationNotFound(reservationId) if (!reservation.isReservedBy(memberId)) {
throw ReservationException(ReservationErrorCode.NOT_RESERVATION_OWNER)
}
reservationRepository.delete(reservation)
} }
fun rejectWaiting(reservationId: Long, memberId: Long) { fun rejectWaiting(reservationId: Long, memberId: Long) {
validateIsMemberAdmin(memberId) validateIsMemberAdmin(memberId)
reservationRepository.findByIdOrNull(reservationId)?.takeIf { val reservation: ReservationEntity = findReservationOrThrow(reservationId)
it.isWaiting()
}?.let { if (!reservation.isWaiting()) {
reservationRepository.delete(it) throw ReservationException(ReservationErrorCode.ALREADY_CONFIRMED)
} ?: throw throwReservationNotFound(reservationId) }
reservationRepository.delete(reservation)
} }
private fun validateIsMemberAdmin(memberId: Long) { private fun validateIsMemberAdmin(memberId: Long) {
memberService.findById(memberId).takeIf { val member: MemberEntity = memberService.findById(memberId)
it.isAdmin() if (member.isAdmin()) {
} ?: throw RoomescapeException( return
ErrorType.PERMISSION_DOES_NOT_EXIST, }
"[memberId: $memberId]", throw ReservationException(ReservationErrorCode.NO_PERMISSION)
HttpStatus.FORBIDDEN
)
} }
private fun throwReservationNotFound(reservationId: Long?): RoomescapeException { private fun findReservationOrThrow(reservationId: Long): ReservationEntity {
return RoomescapeException( return reservationRepository.findByIdOrNull(reservationId)
ErrorType.RESERVATION_NOT_FOUND, ?: throw ReservationException(ReservationErrorCode.RESERVATION_NOT_FOUND)
"[reservationId: $reservationId]",
HttpStatus.NOT_FOUND
)
} }
} }

View File

@ -22,7 +22,7 @@ class ReservationWithPaymentService(
paymentInfo: PaymentApproveResponse, paymentInfo: PaymentApproveResponse,
memberId: Long memberId: Long
): ReservationRetrieveResponse { ): ReservationRetrieveResponse {
val reservation: ReservationEntity = reservationService.addReservation(request, memberId) val reservation: ReservationEntity = reservationService.createConfirmedReservation(request, memberId)
return paymentService.createPayment(paymentInfo, reservation) return paymentService.createPayment(paymentInfo, reservation)
.reservation .reservation

View File

@ -35,7 +35,7 @@ class ReservationEntity(
fun isWaiting(): Boolean = reservationStatus == ReservationStatus.WAITING fun isWaiting(): Boolean = reservationStatus == ReservationStatus.WAITING
@JsonIgnore @JsonIgnore
fun isSameMember(memberId: Long): Boolean { fun isReservedBy(memberId: Long): Boolean {
return this.member.id == memberId return this.member.id == memberId
} }
} }

View File

@ -59,5 +59,5 @@ interface ReservationRepository
ON p.reservation = r ON p.reservation = r
WHERE r.member.id = :memberId WHERE r.member.id = :memberId
""") """)
fun findAllById(memberId: Long): List<MyReservationRetrieveResponse> fun findAllByMemberId(memberId: Long): List<MyReservationRetrieveResponse>
} }