generated from pricelees/issue-pr-template
[#30] 코드 구조 개선 #31
@ -74,6 +74,7 @@ class PaymentWriter(
|
|||||||
|
|
||||||
return canceledPaymentRepository.save(canceledPayment)
|
return canceledPaymentRepository.save(canceledPayment)
|
||||||
.also {
|
.also {
|
||||||
|
paymentRepository.deleteByPaymentKey(paymentKey)
|
||||||
log.debug { "[PaymentWriter.createCanceled] 완료: paymentKey=${paymentKey}, canceledPaymentId=${it.id}" }
|
log.debug { "[PaymentWriter.createCanceled] 완료: paymentKey=${paymentKey}, canceledPaymentId=${it.id}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,5 +8,5 @@ interface PaymentRepository : JpaRepository<PaymentEntity, Long> {
|
|||||||
|
|
||||||
fun findByReservationId(reservationId: Long): PaymentEntity?
|
fun findByReservationId(reservationId: Long): PaymentEntity?
|
||||||
|
|
||||||
fun findByPaymentKey(paymentKey: String): PaymentEntity?
|
fun deleteByPaymentKey(paymentKey: String)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,104 @@
|
|||||||
|
package roomescape.reservation.business
|
||||||
|
|
||||||
|
import io.github.oshai.kotlinlogging.KLogger
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import jakarta.transaction.Transactional
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import roomescape.reservation.implement.ReservationFinder
|
||||||
|
import roomescape.reservation.implement.ReservationWriter
|
||||||
|
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||||
|
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||||
|
import roomescape.reservation.web.*
|
||||||
|
|
||||||
|
private val log: KLogger = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Transactional
|
||||||
|
class ReservationCommandService(
|
||||||
|
private val reservationFinder: ReservationFinder,
|
||||||
|
private val reservationWriter: ReservationWriter
|
||||||
|
) {
|
||||||
|
fun createReservationWithPayment(
|
||||||
|
request: ReservationCreateWithPaymentRequest,
|
||||||
|
memberId: Long
|
||||||
|
): ReservationEntity {
|
||||||
|
log.info { "[ReservationCommandService.createReservationByAdmin] 시작: date=${request.date}, timeId=${request.timeId}, themeId=${request.themeId}, memberId=${memberId}" }
|
||||||
|
|
||||||
|
val created: ReservationEntity = reservationWriter.create(
|
||||||
|
date = request.date,
|
||||||
|
timeId = request.timeId,
|
||||||
|
themeId = request.themeId,
|
||||||
|
status = ReservationStatus.CONFIRMED,
|
||||||
|
memberId = memberId,
|
||||||
|
requesterId = memberId
|
||||||
|
)
|
||||||
|
|
||||||
|
return created.also {
|
||||||
|
log.info { "[ReservationCommandService.createReservationByAdmin] 완료: reservationId=${it.id}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createReservationByAdmin(
|
||||||
|
request: AdminReservationCreateRequest,
|
||||||
|
memberId: Long
|
||||||
|
): ReservationCreateResponse {
|
||||||
|
log.info { "[ReservationCommandService.createReservationByAdmin] 시작: date=${request.date}, timeId=${request.timeId}, themeId=${request.themeId}, memberId=${request.memberId} by adminId=${memberId}" }
|
||||||
|
|
||||||
|
val created: ReservationEntity = reservationWriter.create(
|
||||||
|
date = request.date,
|
||||||
|
timeId = request.timeId,
|
||||||
|
themeId = request.themeId,
|
||||||
|
status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED,
|
||||||
|
memberId = request.memberId,
|
||||||
|
requesterId = memberId
|
||||||
|
)
|
||||||
|
|
||||||
|
return created.toCreateResponse()
|
||||||
|
.also {
|
||||||
|
log.info { "[ReservationCommandService.createReservationByAdmin] 관리자 예약 추가 완료: reservationId=${it.id}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createWaiting(request: WaitingCreateRequest, memberId: Long): ReservationCreateResponse {
|
||||||
|
log.info { "[ReservationCommandService.createWaiting] 시작: date=${request.date}, timeId=${request.timeId}, themeId=${request.themeId}, memberId=${memberId}" }
|
||||||
|
|
||||||
|
val created: ReservationEntity = reservationWriter.create(
|
||||||
|
date = request.date,
|
||||||
|
timeId = request.timeId,
|
||||||
|
themeId = request.themeId,
|
||||||
|
status = ReservationStatus.WAITING,
|
||||||
|
memberId = memberId,
|
||||||
|
requesterId = memberId
|
||||||
|
)
|
||||||
|
|
||||||
|
return created.toCreateResponse()
|
||||||
|
.also {
|
||||||
|
log.info { "[ReservationCommandService.createWaiting] 완료: reservationId=${it.id}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteReservation(reservationId: Long, memberId: Long) {
|
||||||
|
log.debug { "[ReservationCommandService.deleteReservation] 시작: reservationId=${reservationId}, memberId=$memberId" }
|
||||||
|
|
||||||
|
val reservation: ReservationEntity = reservationFinder.findById(reservationId)
|
||||||
|
|
||||||
|
reservationWriter.deleteConfirmed(reservation, requesterId = memberId)
|
||||||
|
.also { log.info { "[ReservationCommandService.deleteReservation] 완료: reservationId=${reservationId}" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun confirmWaiting(reservationId: Long, memberId: Long) {
|
||||||
|
log.info { "[ReservationCommandService.confirmWaiting] 시작: reservationId=$reservationId (by adminId=$memberId)" }
|
||||||
|
|
||||||
|
reservationWriter.confirm(reservationId)
|
||||||
|
.also { log.info { "[ReservationCommandService.confirmWaiting] 완료: reservationId=$reservationId" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteWaiting(reservationId: Long, memberId: Long) {
|
||||||
|
log.info { "[ReservationCommandService.deleteWaiting] 시작: reservationId=$reservationId (by adminId=$memberId)" }
|
||||||
|
|
||||||
|
val reservation: ReservationEntity = reservationFinder.findById(reservationId)
|
||||||
|
|
||||||
|
reservationWriter.deleteWaiting(reservation, requesterId = memberId)
|
||||||
|
.also { log.info { "[ReservationCommandService.deleteWaiting] 완료: reservationId=$reservationId" } }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
package roomescape.reservation.business
|
||||||
|
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import roomescape.reservation.implement.ReservationFinder
|
||||||
|
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||||
|
import roomescape.reservation.web.MyReservationRetrieveListResponse
|
||||||
|
import roomescape.reservation.web.ReservationRetrieveListResponse
|
||||||
|
import roomescape.reservation.web.toRetrieveListResponse
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
private val log = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
class ReservationQueryService(
|
||||||
|
private val reservationFinder: ReservationFinder
|
||||||
|
) {
|
||||||
|
fun findReservations(): ReservationRetrieveListResponse {
|
||||||
|
log.info { "[ReservationService.findReservations] 시작" }
|
||||||
|
|
||||||
|
return reservationFinder.findAllByStatuses(*ReservationStatus.confirmedStatus())
|
||||||
|
.toRetrieveListResponse()
|
||||||
|
.also { log.info { "[ReservationService.findReservations] ${it.reservations.size}개의 예약 조회 완료" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findAllWaiting(): ReservationRetrieveListResponse {
|
||||||
|
log.info { "[ReservationService.findAllWaiting] 시작" }
|
||||||
|
|
||||||
|
return reservationFinder.findAllByStatuses(ReservationStatus.WAITING)
|
||||||
|
.toRetrieveListResponse()
|
||||||
|
.also { log.info { "[ReservationService.findAllWaiting] ${it.reservations.size}개의 대기 조회 완료" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findReservationsByMemberId(memberId: Long): MyReservationRetrieveListResponse {
|
||||||
|
log.info { "[ReservationService.findReservationsByMemberId] 시작: memberId=$memberId" }
|
||||||
|
|
||||||
|
return reservationFinder.findAllByMemberId(memberId)
|
||||||
|
.toRetrieveListResponse()
|
||||||
|
.also { log.info { "[ReservationService.findReservationsByMemberId] ${it.reservations.size}개의 예약 조회 완료: memberId=$memberId" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchReservations(
|
||||||
|
themeId: Long?,
|
||||||
|
memberId: Long?,
|
||||||
|
startFrom: LocalDate?,
|
||||||
|
endAt: LocalDate?,
|
||||||
|
): ReservationRetrieveListResponse {
|
||||||
|
log.info { "[ReservationService.searchReservations] 시작: themeId=$themeId, memberId=$memberId, dateFrom=$startFrom, dateTo=$endAt" }
|
||||||
|
|
||||||
|
return reservationFinder.searchReservations(themeId, memberId, startFrom, endAt)
|
||||||
|
.toRetrieveListResponse()
|
||||||
|
.also { log.info { "[ReservationService.searchReservations] ${it.reservations.size}개의 예약 조회 완료: themeId=$themeId, memberId=$memberId, dateFrom=$startFrom, dateTo=$endAt" } }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,322 +0,0 @@
|
|||||||
package roomescape.reservation.business
|
|
||||||
|
|
||||||
import com.github.f4b6a3.tsid.TsidFactory
|
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
|
||||||
import org.springframework.data.jpa.domain.Specification
|
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import org.springframework.transaction.annotation.Transactional
|
|
||||||
import roomescape.common.config.next
|
|
||||||
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.ReservationRepository
|
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationSearchSpecification
|
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
|
||||||
import roomescape.reservation.web.*
|
|
||||||
import roomescape.theme.business.ThemeService
|
|
||||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
|
||||||
import roomescape.time.business.TimeService
|
|
||||||
import roomescape.time.infrastructure.persistence.TimeEntity
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
|
|
||||||
private val log = KotlinLogging.logger {}
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@Transactional
|
|
||||||
class ReservationService(
|
|
||||||
private val tsidFactory: TsidFactory,
|
|
||||||
private val reservationRepository: ReservationRepository,
|
|
||||||
private val timeService: TimeService,
|
|
||||||
private val memberService: MemberService,
|
|
||||||
private val themeService: ThemeService,
|
|
||||||
) {
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
fun findReservations(): ReservationRetrieveListResponse {
|
|
||||||
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
|
||||||
.confirmed()
|
|
||||||
.build()
|
|
||||||
val reservations = findAllReservationByStatus(spec)
|
|
||||||
log.info { "[ReservationService.findReservations] ${reservations.size} 개의 확정 예약 조회 완료" }
|
|
||||||
|
|
||||||
return ReservationRetrieveListResponse(reservations)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
fun findAllWaiting(): ReservationRetrieveListResponse {
|
|
||||||
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
|
||||||
.waiting()
|
|
||||||
.build()
|
|
||||||
val reservations = findAllReservationByStatus(spec)
|
|
||||||
log.info { "[ReservationService.findAllWaiting] ${reservations.size} 개의 대기 예약 조회 완료" }
|
|
||||||
|
|
||||||
return ReservationRetrieveListResponse(reservations)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findAllReservationByStatus(spec: Specification<ReservationEntity>): List<ReservationRetrieveResponse> {
|
|
||||||
return reservationRepository.findAll(spec).map { it.toRetrieveResponse() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteReservation(reservationId: Long, memberId: Long) {
|
|
||||||
validateIsMemberAdmin(memberId, "deleteReservation")
|
|
||||||
log.debug { "[ReservationService.deleteReservation] 예약 삭제 시작: reservationId=$reservationId, memberId=$memberId" }
|
|
||||||
reservationRepository.deleteById(reservationId)
|
|
||||||
log.info { "[ReservationService.deleteReservation] 예약 삭제 완료: reservationId=$reservationId" }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createConfirmedReservation(
|
|
||||||
request: ReservationCreateWithPaymentRequest,
|
|
||||||
memberId: Long,
|
|
||||||
): ReservationEntity {
|
|
||||||
val themeId = request.themeId
|
|
||||||
val timeId = request.timeId
|
|
||||||
val date: LocalDate = request.date
|
|
||||||
validateIsReservationExist(themeId, timeId, date, "createConfirmedReservation")
|
|
||||||
|
|
||||||
log.debug { "[ReservationService.createConfirmedReservation] 예약 추가 시작: memberId=$memberId, themeId=${request.themeId}, timeId=${request.timeId}, date=${request.date}" }
|
|
||||||
val reservation: ReservationEntity =
|
|
||||||
createEntity(timeId, themeId, date, memberId, ReservationStatus.CONFIRMED)
|
|
||||||
|
|
||||||
return reservationRepository.save(reservation)
|
|
||||||
.also { log.info { "[ReservationService.createConfirmedReservation] 예약 추가 완료: reservationId=${it.id}, status=${it.status}" } }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createReservationByAdmin(request: AdminReservationCreateRequest): ReservationRetrieveResponse {
|
|
||||||
validateIsReservationExist(request.themeId, request.timeId, request.date)
|
|
||||||
|
|
||||||
log.debug { "[ReservationService.createReservationByAdmin] 관리자의 예약 추가: memberId=${request.memberId}, themeId=${request.themeId}, timeId=${request.timeId}, date=${request.date}" }
|
|
||||||
return addReservationWithoutPayment(
|
|
||||||
request.themeId,
|
|
||||||
request.timeId,
|
|
||||||
request.date,
|
|
||||||
request.memberId,
|
|
||||||
ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
|
||||||
).also {
|
|
||||||
log.info { "[ReservationService.createReservationByAdmin] 관리자 예약 추가 완료: reservationId=${it.id}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createWaiting(request: WaitingCreateRequest, memberId: Long): ReservationRetrieveResponse {
|
|
||||||
validateMemberAlreadyReserve(request.themeId, request.timeId, request.date, memberId)
|
|
||||||
log.debug { "[ReservationService.createWaiting] 예약 대기 추가 시작: memberId=$memberId, themeId=${request.themeId}, timeId=${request.timeId}, date=${request.date}" }
|
|
||||||
return addReservationWithoutPayment(
|
|
||||||
request.themeId,
|
|
||||||
request.timeId,
|
|
||||||
request.date,
|
|
||||||
memberId,
|
|
||||||
ReservationStatus.WAITING
|
|
||||||
).also {
|
|
||||||
log.info { "[ReservationService.createWaiting] 예약 대기 추가 완료: reservationId=${it.id}, status=${it.status}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addReservationWithoutPayment(
|
|
||||||
themeId: Long,
|
|
||||||
timeId: Long,
|
|
||||||
date: LocalDate,
|
|
||||||
memberId: Long,
|
|
||||||
status: ReservationStatus,
|
|
||||||
): ReservationRetrieveResponse = createEntity(timeId, themeId, date, memberId, status)
|
|
||||||
.also {
|
|
||||||
reservationRepository.save(it)
|
|
||||||
}.toRetrieveResponse()
|
|
||||||
|
|
||||||
private fun validateMemberAlreadyReserve(themeId: Long, timeId: Long, date: LocalDate, memberId: Long) {
|
|
||||||
log.debug {
|
|
||||||
"[ReservationService.validateMemberAlreadyReserve] 회원의 중복 예약 여부 확인: themeId=$themeId, timeId=$timeId, date=$date, memberId=$memberId"
|
|
||||||
}
|
|
||||||
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
|
||||||
.sameMemberId(memberId)
|
|
||||||
.sameThemeId(themeId)
|
|
||||||
.sameTimeId(timeId)
|
|
||||||
.sameDate(date)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
if (reservationRepository.exists(spec)) {
|
|
||||||
log.warn { "[ReservationService.validateMemberAlreadyReserve] 중복된 예약 존재: themeId=$themeId, timeId=$timeId, date=$date" }
|
|
||||||
throw ReservationException(ReservationErrorCode.ALREADY_RESERVE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validateIsReservationExist(
|
|
||||||
themeId: Long,
|
|
||||||
timeId: Long,
|
|
||||||
date: LocalDate,
|
|
||||||
calledBy: String = "validateIsReservationExist"
|
|
||||||
) {
|
|
||||||
log.debug {
|
|
||||||
"[ReservationService.$calledBy] 예약 존재 여부 확인: themeId=$themeId, timeId=$timeId, date=$date"
|
|
||||||
}
|
|
||||||
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
|
||||||
.confirmed()
|
|
||||||
.sameThemeId(themeId)
|
|
||||||
.sameTimeId(timeId)
|
|
||||||
.sameDate(date)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
if (reservationRepository.exists(spec)) {
|
|
||||||
log.warn { "[ReservationService.$calledBy] 중복된 예약 존재: themeId=$themeId, timeId=$timeId, date=$date" }
|
|
||||||
throw ReservationException(ReservationErrorCode.RESERVATION_DUPLICATED)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validateDateAndTime(
|
|
||||||
requestDate: LocalDate,
|
|
||||||
requestTime: TimeEntity,
|
|
||||||
) {
|
|
||||||
val now = LocalDateTime.now()
|
|
||||||
val request = LocalDateTime.of(requestDate, requestTime.startAt)
|
|
||||||
|
|
||||||
if (request.isBefore(now)) {
|
|
||||||
log.info { "[ReservationService.validateDateAndTime] 날짜 범위 오류. request=$request, now=$now" }
|
|
||||||
throw ReservationException(ReservationErrorCode.PAST_REQUEST_DATETIME)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createEntity(
|
|
||||||
timeId: Long,
|
|
||||||
themeId: Long,
|
|
||||||
date: LocalDate,
|
|
||||||
memberId: Long,
|
|
||||||
status: ReservationStatus,
|
|
||||||
): ReservationEntity {
|
|
||||||
val time: TimeEntity = timeService.findById(timeId)
|
|
||||||
val theme: ThemeEntity = themeService.findById(themeId)
|
|
||||||
val member: MemberEntity = memberService.findById(memberId)
|
|
||||||
|
|
||||||
validateDateAndTime(date, time)
|
|
||||||
|
|
||||||
return ReservationEntity(
|
|
||||||
_id = tsidFactory.next(),
|
|
||||||
date = date,
|
|
||||||
time = time,
|
|
||||||
theme = theme,
|
|
||||||
member = member,
|
|
||||||
status = status
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
fun searchReservations(
|
|
||||||
themeId: Long?,
|
|
||||||
memberId: Long?,
|
|
||||||
dateFrom: LocalDate?,
|
|
||||||
dateTo: LocalDate?,
|
|
||||||
): ReservationRetrieveListResponse {
|
|
||||||
log.debug { "[ReservationService.searchReservations] 예약 검색 시작: themeId=$themeId, memberId=$memberId, dateFrom=$dateFrom, dateTo=$dateTo" }
|
|
||||||
validateSearchDateRange(dateFrom, dateTo)
|
|
||||||
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
|
||||||
.confirmed()
|
|
||||||
.sameThemeId(themeId)
|
|
||||||
.sameMemberId(memberId)
|
|
||||||
.dateStartFrom(dateFrom)
|
|
||||||
.dateEndAt(dateTo)
|
|
||||||
.build()
|
|
||||||
val reservations = findAllReservationByStatus(spec)
|
|
||||||
|
|
||||||
return ReservationRetrieveListResponse(reservations)
|
|
||||||
.also { log.info { "[ReservationService.searchReservations] 예약 ${reservations.size}개 조회 완료: themeId=$themeId, memberId=$memberId, dateFrom=$dateFrom, dateTo=$dateTo" } }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validateSearchDateRange(startFrom: LocalDate?, endAt: LocalDate?) {
|
|
||||||
if (startFrom == null || endAt == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (startFrom.isAfter(endAt)) {
|
|
||||||
log.info { "[ReservationService.validateSearchDateRange] 조회 범위 오류: startFrom=$startFrom, endAt=$endAt" }
|
|
||||||
throw ReservationException(ReservationErrorCode.INVALID_SEARCH_DATE_RANGE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
fun findReservationsByMemberId(memberId: Long): MyReservationRetrieveListResponse {
|
|
||||||
val reservations = reservationRepository.findAllByMemberId(memberId)
|
|
||||||
log.info { "[ReservationService.findReservationsByMemberId] memberId=${memberId}인 ${reservations.size}개의 예약 조회 완료" }
|
|
||||||
return MyReservationRetrieveListResponse(reservations)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun confirmWaiting(reservationId: Long, memberId: Long) {
|
|
||||||
log.debug { "[ReservationService.confirmWaiting] 대기 예약 승인 시작: reservationId=$reservationId (by adminId=$memberId)" }
|
|
||||||
validateIsMemberAdmin(memberId, "confirmWaiting")
|
|
||||||
|
|
||||||
log.debug { "[ReservationService.confirmWaiting] 대기 여부 확인 시작: reservationId=$reservationId" }
|
|
||||||
if (reservationRepository.isExistConfirmedReservation(reservationId)) {
|
|
||||||
log.warn { "[ReservationService.confirmWaiting] 승인 실패(이미 확정된 예약 존재): reservationId=$reservationId" }
|
|
||||||
throw ReservationException(ReservationErrorCode.CONFIRMED_RESERVATION_ALREADY_EXISTS)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug { "[ReservationService.confirmWaiting] 대기 예약 상태 변경 시작: reservationId=$reservationId" }
|
|
||||||
reservationRepository.updateStatusByReservationId(reservationId, ReservationStatus.CONFIRMED_PAYMENT_REQUIRED)
|
|
||||||
log.debug { "[ReservationService.confirmWaiting] 대기 예약 상태 변경 완료: reservationId=$reservationId, status=${ReservationStatus.CONFIRMED_PAYMENT_REQUIRED}" }
|
|
||||||
|
|
||||||
log.info { "[ReservationService.confirmWaiting] 대기 예약 승인 완료: reservationId=$reservationId" }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteWaiting(reservationId: Long, memberId: Long) {
|
|
||||||
log.debug { "[ReservationService.deleteWaiting] 대기 취소 시작: reservationId=$reservationId, memberId=$memberId" }
|
|
||||||
|
|
||||||
val reservation: ReservationEntity = findReservationOrThrow(reservationId, "deleteWaiting")
|
|
||||||
if (!reservation.isWaiting()) {
|
|
||||||
log.warn {
|
|
||||||
"[ReservationService.deleteWaiting] 대기 취소 실패(대기 예약이 아님): reservationId=$reservationId" +
|
|
||||||
", currentStatus=${reservation.status} memberId=$memberId"
|
|
||||||
}
|
|
||||||
throw ReservationException(ReservationErrorCode.ALREADY_CONFIRMED)
|
|
||||||
}
|
|
||||||
if (!reservation.isReservedBy(memberId)) {
|
|
||||||
log.error {
|
|
||||||
"[ReservationService.deleteWaiting] 대기 취소 실패(예약자 본인의 취소 요청이 아님): reservationId=$reservationId" +
|
|
||||||
", memberId=$memberId "
|
|
||||||
}
|
|
||||||
throw ReservationException(ReservationErrorCode.NOT_RESERVATION_OWNER)
|
|
||||||
}
|
|
||||||
log.debug { "[ReservationService.deleteWaiting] 대기 예약 삭제 시작: reservationId=$reservationId" }
|
|
||||||
reservationRepository.delete(reservation)
|
|
||||||
|
|
||||||
log.info { "[ReservationService.deleteWaiting] 대기 취소 완료: reservationId=$reservationId, memberId=$memberId" }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun rejectWaiting(reservationId: Long, memberId: Long) {
|
|
||||||
validateIsMemberAdmin(memberId, "rejectWaiting")
|
|
||||||
log.debug { "[ReservationService.rejectWaiting] 대기 예약 삭제 시작: reservationId=$reservationId (by adminId=$memberId)" }
|
|
||||||
val reservation: ReservationEntity = findReservationOrThrow(reservationId, "rejectWaiting")
|
|
||||||
|
|
||||||
if (!reservation.isWaiting()) {
|
|
||||||
log.warn {
|
|
||||||
"[ReservationService.rejectWaiting] 대기 예약 삭제 실패(이미 확정 상태): reservationId=$reservationId" +
|
|
||||||
", status=${reservation.status}"
|
|
||||||
}
|
|
||||||
throw ReservationException(ReservationErrorCode.ALREADY_CONFIRMED)
|
|
||||||
}
|
|
||||||
reservationRepository.delete(reservation)
|
|
||||||
log.info { "[ReservationService.rejectWaiting] 대기 예약 삭제 완료: reservationId=$reservationId" }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validateIsMemberAdmin(memberId: Long, calledBy: String = "validateIsMemberAdmin") {
|
|
||||||
log.debug { "[ReservationService.$calledBy] 관리자 여부 확인: memberId=$memberId" }
|
|
||||||
val member: MemberEntity = memberService.findById(memberId)
|
|
||||||
if (member.isAdmin()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.warn { "[ReservationService.$calledBy] 관리자가 아님: memberId=$memberId, role=${member.role}" }
|
|
||||||
throw ReservationException(ReservationErrorCode.NO_PERMISSION)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findReservationOrThrow(
|
|
||||||
reservationId: Long,
|
|
||||||
calledBy: String = "findReservationOrThrow"
|
|
||||||
): ReservationEntity {
|
|
||||||
log.debug { "[ReservationService.$calledBy] 예약 조회: reservationId=$reservationId" }
|
|
||||||
return reservationRepository.findByIdOrNull(reservationId)
|
|
||||||
?.also { log.info { "[ReservationService.$calledBy] 예약 조회 완료: reservationId=$reservationId" } }
|
|
||||||
?: run {
|
|
||||||
log.warn { "[ReservationService.$calledBy] 예약 조회 실패: reservationId=$reservationId" }
|
|
||||||
throw ReservationException(ReservationErrorCode.RESERVATION_NOT_FOUND)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -18,7 +18,7 @@ private val log = KotlinLogging.logger {}
|
|||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
class ReservationWithPaymentService(
|
class ReservationWithPaymentService(
|
||||||
private val reservationService: ReservationService,
|
private val reservationCommandService: ReservationCommandService,
|
||||||
private val paymentService: PaymentService,
|
private val paymentService: PaymentService,
|
||||||
) {
|
) {
|
||||||
fun createReservationAndPayment(
|
fun createReservationAndPayment(
|
||||||
@ -28,7 +28,7 @@ class ReservationWithPaymentService(
|
|||||||
): ReservationCreateResponse {
|
): ReservationCreateResponse {
|
||||||
log.info { "[ReservationWithPaymentService.createReservationAndPayment] 예약 & 결제 정보 저장 시작: memberId=$memberId, paymentInfo=$approvedPaymentInfo" }
|
log.info { "[ReservationWithPaymentService.createReservationAndPayment] 예약 & 결제 정보 저장 시작: memberId=$memberId, paymentInfo=$approvedPaymentInfo" }
|
||||||
|
|
||||||
val reservation: ReservationEntity = reservationService.createConfirmedReservation(request, memberId)
|
val reservation: ReservationEntity = reservationCommandService.createReservationWithPayment(request, memberId)
|
||||||
.also { paymentService.createPayment(approvedPaymentInfo, it) }
|
.also { paymentService.createPayment(approvedPaymentInfo, it) }
|
||||||
|
|
||||||
return reservation.toCreateResponse()
|
return reservation.toCreateResponse()
|
||||||
@ -50,7 +50,7 @@ class ReservationWithPaymentService(
|
|||||||
log.info { "[ReservationWithPaymentService.deleteReservationAndPayment] 결제 취소 정보 저장 & 예약 삭제 시작: reservationId=$reservationId" }
|
log.info { "[ReservationWithPaymentService.deleteReservationAndPayment] 결제 취소 정보 저장 & 예약 삭제 시작: reservationId=$reservationId" }
|
||||||
val paymentCancelRequest = paymentService.createCanceledPayment(reservationId)
|
val paymentCancelRequest = paymentService.createCanceledPayment(reservationId)
|
||||||
|
|
||||||
reservationService.deleteReservation(reservationId, memberId)
|
reservationCommandService.deleteReservation(reservationId, memberId)
|
||||||
log.info { "[ReservationWithPaymentService.deleteReservationAndPayment] 결제 취소 정보 저장 & 예약 삭제 완료: reservationId=$reservationId" }
|
log.info { "[ReservationWithPaymentService.deleteReservationAndPayment] 결제 취소 정보 저장 & 예약 삭제 완료: reservationId=$reservationId" }
|
||||||
return paymentCancelRequest
|
return paymentCancelRequest
|
||||||
}
|
}
|
||||||
@ -62,7 +62,7 @@ class ReservationWithPaymentService(
|
|||||||
val notPaid: Boolean = !paymentService.existsByReservationId(reservationId)
|
val notPaid: Boolean = !paymentService.existsByReservationId(reservationId)
|
||||||
|
|
||||||
return notPaid.also {
|
return notPaid.also {
|
||||||
log.info { "[ReservationWithPaymentService.isNotPaidReservation] 완료: reservationId=$reservationId, 미결제=${notPaid}" }
|
log.info { "[ReservationWithPaymentService.isNotPaidReservation] 완료: reservationId=$reservationId, isPaid=${notPaid}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -92,7 +92,8 @@ interface ReservationAPI {
|
|||||||
)
|
)
|
||||||
fun createReservationByAdmin(
|
fun createReservationByAdmin(
|
||||||
@Valid @RequestBody adminReservationRequest: AdminReservationCreateRequest,
|
@Valid @RequestBody adminReservationRequest: AdminReservationCreateRequest,
|
||||||
): ResponseEntity<CommonApiResponse<ReservationRetrieveResponse>>
|
@MemberId @Parameter(hidden = true) memberId: Long
|
||||||
|
): ResponseEntity<CommonApiResponse<ReservationCreateResponse>>
|
||||||
|
|
||||||
@Admin
|
@Admin
|
||||||
@Operation(summary = "모든 예약 대기 조회", tags = ["관리자 로그인이 필요한 API"])
|
@Operation(summary = "모든 예약 대기 조회", tags = ["관리자 로그인이 필요한 API"])
|
||||||
@ -116,7 +117,7 @@ interface ReservationAPI {
|
|||||||
fun createWaiting(
|
fun createWaiting(
|
||||||
@Valid @RequestBody waitingCreateRequest: WaitingCreateRequest,
|
@Valid @RequestBody waitingCreateRequest: WaitingCreateRequest,
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
): ResponseEntity<CommonApiResponse<ReservationRetrieveResponse>>
|
): ResponseEntity<CommonApiResponse<ReservationCreateResponse>>
|
||||||
|
|
||||||
@LoginRequired
|
@LoginRequired
|
||||||
@Operation(summary = "예약 대기 취소", tags = ["로그인이 필요한 API"])
|
@Operation(summary = "예약 대기 취소", tags = ["로그인이 필요한 API"])
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package roomescape.reservation.infrastructure.persistence
|
package roomescape.reservation.infrastructure.persistence
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.domain.Specification
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor
|
||||||
import org.springframework.data.jpa.repository.Modifying
|
import org.springframework.data.jpa.repository.Modifying
|
||||||
|
|||||||
@ -10,7 +10,8 @@ import roomescape.payment.infrastructure.client.PaymentApproveRequest
|
|||||||
import roomescape.payment.infrastructure.client.PaymentApproveResponse
|
import roomescape.payment.infrastructure.client.PaymentApproveResponse
|
||||||
import roomescape.payment.infrastructure.client.TossPaymentClient
|
import roomescape.payment.infrastructure.client.TossPaymentClient
|
||||||
import roomescape.payment.web.PaymentCancelRequest
|
import roomescape.payment.web.PaymentCancelRequest
|
||||||
import roomescape.reservation.business.ReservationService
|
import roomescape.reservation.business.ReservationCommandService
|
||||||
|
import roomescape.reservation.business.ReservationQueryService
|
||||||
import roomescape.reservation.business.ReservationWithPaymentService
|
import roomescape.reservation.business.ReservationWithPaymentService
|
||||||
import roomescape.reservation.docs.ReservationAPI
|
import roomescape.reservation.docs.ReservationAPI
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
@ -19,12 +20,13 @@ import java.time.LocalDate
|
|||||||
@RestController
|
@RestController
|
||||||
class ReservationController(
|
class ReservationController(
|
||||||
private val reservationWithPaymentService: ReservationWithPaymentService,
|
private val reservationWithPaymentService: ReservationWithPaymentService,
|
||||||
private val reservationService: ReservationService,
|
private val reservationQueryService: ReservationQueryService,
|
||||||
|
private val reservationCommandService: ReservationCommandService,
|
||||||
private val paymentClient: TossPaymentClient
|
private val paymentClient: TossPaymentClient
|
||||||
) : ReservationAPI {
|
) : ReservationAPI {
|
||||||
@GetMapping("/reservations")
|
@GetMapping("/reservations")
|
||||||
override fun findReservations(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
|
override fun findReservations(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
|
||||||
val response: ReservationRetrieveListResponse = reservationService.findReservations()
|
val response: ReservationRetrieveListResponse = reservationQueryService.findReservations()
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
}
|
}
|
||||||
@ -33,7 +35,7 @@ class ReservationController(
|
|||||||
override fun findReservationsByMemberId(
|
override fun findReservationsByMemberId(
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long
|
@MemberId @Parameter(hidden = true) memberId: Long
|
||||||
): ResponseEntity<CommonApiResponse<MyReservationRetrieveListResponse>> {
|
): ResponseEntity<CommonApiResponse<MyReservationRetrieveListResponse>> {
|
||||||
val response: MyReservationRetrieveListResponse = reservationService.findReservationsByMemberId(memberId)
|
val response: MyReservationRetrieveListResponse = reservationQueryService.findReservationsByMemberId(memberId)
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
}
|
}
|
||||||
@ -45,7 +47,7 @@ class ReservationController(
|
|||||||
@RequestParam(required = false) dateFrom: LocalDate?,
|
@RequestParam(required = false) dateFrom: LocalDate?,
|
||||||
@RequestParam(required = false) dateTo: LocalDate?
|
@RequestParam(required = false) dateTo: LocalDate?
|
||||||
): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
|
): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
|
||||||
val response: ReservationRetrieveListResponse = reservationService.searchReservations(
|
val response: ReservationRetrieveListResponse = reservationQueryService.searchReservations(
|
||||||
themeId,
|
themeId,
|
||||||
memberId,
|
memberId,
|
||||||
dateFrom,
|
dateFrom,
|
||||||
@ -61,7 +63,7 @@ class ReservationController(
|
|||||||
@PathVariable("id") reservationId: Long
|
@PathVariable("id") reservationId: Long
|
||||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||||
if (reservationWithPaymentService.isNotPaidReservation(reservationId)) {
|
if (reservationWithPaymentService.isNotPaidReservation(reservationId)) {
|
||||||
reservationService.deleteReservation(reservationId, memberId)
|
reservationCommandService.deleteReservation(reservationId, memberId)
|
||||||
return ResponseEntity.noContent().build()
|
return ResponseEntity.noContent().build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,10 +112,11 @@ class ReservationController(
|
|||||||
|
|
||||||
@PostMapping("/reservations/admin")
|
@PostMapping("/reservations/admin")
|
||||||
override fun createReservationByAdmin(
|
override fun createReservationByAdmin(
|
||||||
@Valid @RequestBody adminReservationRequest: AdminReservationCreateRequest
|
@Valid @RequestBody adminReservationRequest: AdminReservationCreateRequest,
|
||||||
): ResponseEntity<CommonApiResponse<ReservationRetrieveResponse>> {
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
val response: ReservationRetrieveResponse =
|
): ResponseEntity<CommonApiResponse<ReservationCreateResponse>> {
|
||||||
reservationService.createReservationByAdmin(adminReservationRequest)
|
val response: ReservationCreateResponse =
|
||||||
|
reservationCommandService.createReservationByAdmin(adminReservationRequest, memberId)
|
||||||
|
|
||||||
return ResponseEntity.created(URI.create("/reservations/${response.id}"))
|
return ResponseEntity.created(URI.create("/reservations/${response.id}"))
|
||||||
.body(CommonApiResponse(response))
|
.body(CommonApiResponse(response))
|
||||||
@ -121,7 +124,7 @@ class ReservationController(
|
|||||||
|
|
||||||
@GetMapping("/reservations/waiting")
|
@GetMapping("/reservations/waiting")
|
||||||
override fun findAllWaiting(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
|
override fun findAllWaiting(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
|
||||||
val response: ReservationRetrieveListResponse = reservationService.findAllWaiting()
|
val response: ReservationRetrieveListResponse = reservationQueryService.findAllWaiting()
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
}
|
}
|
||||||
@ -130,8 +133,8 @@ class ReservationController(
|
|||||||
override fun createWaiting(
|
override fun createWaiting(
|
||||||
@Valid @RequestBody waitingCreateRequest: WaitingCreateRequest,
|
@Valid @RequestBody waitingCreateRequest: WaitingCreateRequest,
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
): ResponseEntity<CommonApiResponse<ReservationRetrieveResponse>> {
|
): ResponseEntity<CommonApiResponse<ReservationCreateResponse>> {
|
||||||
val response: ReservationRetrieveResponse = reservationService.createWaiting(
|
val response: ReservationCreateResponse = reservationCommandService.createWaiting(
|
||||||
waitingCreateRequest,
|
waitingCreateRequest,
|
||||||
memberId
|
memberId
|
||||||
)
|
)
|
||||||
@ -145,7 +148,7 @@ class ReservationController(
|
|||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
@PathVariable("id") reservationId: Long
|
@PathVariable("id") reservationId: Long
|
||||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||||
reservationService.deleteWaiting(reservationId, memberId)
|
reservationCommandService.deleteWaiting(reservationId, memberId)
|
||||||
|
|
||||||
return ResponseEntity.noContent().build()
|
return ResponseEntity.noContent().build()
|
||||||
}
|
}
|
||||||
@ -155,7 +158,7 @@ class ReservationController(
|
|||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
@PathVariable("id") reservationId: Long
|
@PathVariable("id") reservationId: Long
|
||||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||||
reservationService.confirmWaiting(reservationId, memberId)
|
reservationCommandService.confirmWaiting(reservationId, memberId)
|
||||||
|
|
||||||
return ResponseEntity.ok().build()
|
return ResponseEntity.ok().build()
|
||||||
}
|
}
|
||||||
@ -165,7 +168,7 @@ class ReservationController(
|
|||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
@PathVariable("id") reservationId: Long
|
@PathVariable("id") reservationId: Long
|
||||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||||
reservationService.rejectWaiting(reservationId, memberId)
|
reservationCommandService.deleteWaiting(reservationId, memberId)
|
||||||
|
|
||||||
return ResponseEntity.noContent().build()
|
return ResponseEntity.noContent().build()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,36 +38,6 @@ class PaymentRepositoryTest(
|
|||||||
.also { it shouldBe false }
|
.also { it shouldBe false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context("findByPaymentKey") {
|
|
||||||
lateinit var payment: PaymentEntity
|
|
||||||
|
|
||||||
beforeTest {
|
|
||||||
reservation = setupReservation()
|
|
||||||
payment = PaymentFixture.create(reservation = reservation)
|
|
||||||
.also { paymentRepository.save(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
test("정상 반환") {
|
|
||||||
paymentRepository.findByPaymentKey(payment.paymentKey)
|
|
||||||
?.also {
|
|
||||||
assertSoftly(it) {
|
|
||||||
this.id shouldBe payment.id
|
|
||||||
this.orderId shouldBe payment.orderId
|
|
||||||
this.paymentKey shouldBe payment.paymentKey
|
|
||||||
this.totalAmount shouldBe payment.totalAmount
|
|
||||||
this.reservation.id shouldBe payment.reservation.id
|
|
||||||
this.approvedAt shouldBe payment.approvedAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?: throw AssertionError("Unexpected null value")
|
|
||||||
}
|
|
||||||
|
|
||||||
test("null 반환") {
|
|
||||||
paymentRepository.findByPaymentKey("non-existent-key")
|
|
||||||
.also { it shouldBe null }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupReservation(): ReservationEntity {
|
private fun setupReservation(): ReservationEntity {
|
||||||
|
|||||||
@ -1,287 +0,0 @@
|
|||||||
package roomescape.reservation.business
|
|
||||||
|
|
||||||
import io.kotest.assertions.throwables.shouldThrow
|
|
||||||
import io.kotest.core.spec.style.FunSpec
|
|
||||||
import io.kotest.matchers.shouldBe
|
|
||||||
import io.mockk.every
|
|
||||||
import io.mockk.mockk
|
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
|
||||||
import roomescape.member.business.MemberService
|
|
||||||
import roomescape.member.infrastructure.persistence.Role
|
|
||||||
import roomescape.reservation.exception.ReservationErrorCode
|
|
||||||
import roomescape.reservation.exception.ReservationException
|
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
|
||||||
import roomescape.theme.business.ThemeService
|
|
||||||
import roomescape.time.business.TimeService
|
|
||||||
import roomescape.util.MemberFixture
|
|
||||||
import roomescape.util.ReservationFixture
|
|
||||||
import roomescape.util.TsidFactory
|
|
||||||
import roomescape.util.TimeFixture
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.LocalTime
|
|
||||||
|
|
||||||
class ReservationServiceTest : FunSpec({
|
|
||||||
|
|
||||||
val reservationRepository: ReservationRepository = mockk()
|
|
||||||
val timeService: TimeService = mockk()
|
|
||||||
val memberService: MemberService = mockk()
|
|
||||||
val themeService: ThemeService = mockk()
|
|
||||||
val reservationService = ReservationService(
|
|
||||||
TsidFactory,
|
|
||||||
reservationRepository,
|
|
||||||
timeService,
|
|
||||||
memberService,
|
|
||||||
themeService
|
|
||||||
)
|
|
||||||
|
|
||||||
context("예약을 추가할 때") {
|
|
||||||
test("이미 예약이 있으면 예외를 던진다.") {
|
|
||||||
every {
|
|
||||||
reservationRepository.exists(any())
|
|
||||||
} returns true
|
|
||||||
|
|
||||||
val reservationRequest = ReservationFixture.createRequest()
|
|
||||||
|
|
||||||
shouldThrow<ReservationException> {
|
|
||||||
reservationService.createConfirmedReservation(reservationRequest, 1L)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe ReservationErrorCode.RESERVATION_DUPLICATED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("날짜, 시간이 잘못 입력되면 예외를 던진다.") {
|
|
||||||
every {
|
|
||||||
reservationRepository.exists(any())
|
|
||||||
} returns false
|
|
||||||
|
|
||||||
every {
|
|
||||||
themeService.findById(any())
|
|
||||||
} returns mockk()
|
|
||||||
|
|
||||||
every {
|
|
||||||
memberService.findById(any())
|
|
||||||
} returns mockk()
|
|
||||||
|
|
||||||
|
|
||||||
test("지난 날짜이면 예외를 던진다.") {
|
|
||||||
val reservationRequest = ReservationFixture.createRequest().copy(
|
|
||||||
date = LocalDate.now().minusDays(1)
|
|
||||||
)
|
|
||||||
|
|
||||||
every {
|
|
||||||
timeService.findById(any())
|
|
||||||
} returns TimeFixture.create()
|
|
||||||
|
|
||||||
shouldThrow<ReservationException> {
|
|
||||||
reservationService.createConfirmedReservation(reservationRequest, 1L)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe ReservationErrorCode.PAST_REQUEST_DATETIME
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test("지난 시간이면 예외를 던진다.") {
|
|
||||||
val reservationRequest = ReservationFixture.createRequest().copy(
|
|
||||||
date = LocalDate.now(),
|
|
||||||
)
|
|
||||||
|
|
||||||
every {
|
|
||||||
timeService.findById(reservationRequest.timeId)
|
|
||||||
} returns TimeFixture.create(
|
|
||||||
startAt = LocalTime.now().minusMinutes(1)
|
|
||||||
)
|
|
||||||
|
|
||||||
shouldThrow<ReservationException> {
|
|
||||||
reservationService.createConfirmedReservation(reservationRequest, 1L)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe ReservationErrorCode.PAST_REQUEST_DATETIME
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("예약 대기를 걸 때") {
|
|
||||||
test("이미 예약한 회원이 같은 날짜와 테마로 대기를 걸면 예외를 던진다.") {
|
|
||||||
val reservationRequest = ReservationFixture.createRequest().copy(
|
|
||||||
date = LocalDate.now(),
|
|
||||||
themeId = 1L,
|
|
||||||
timeId = 1L,
|
|
||||||
)
|
|
||||||
|
|
||||||
every {
|
|
||||||
reservationRepository.exists(any())
|
|
||||||
} returns true
|
|
||||||
|
|
||||||
shouldThrow<ReservationException> {
|
|
||||||
val waitingRequest = ReservationFixture.createWaitingRequest(
|
|
||||||
date = reservationRequest.date,
|
|
||||||
themeId = reservationRequest.themeId,
|
|
||||||
timeId = reservationRequest.timeId
|
|
||||||
)
|
|
||||||
reservationService.createWaiting(waitingRequest, 1L)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe ReservationErrorCode.ALREADY_RESERVE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("예약 대기를 취소할 때") {
|
|
||||||
val reservationId = 1L
|
|
||||||
val member = MemberFixture.create(id = 1L, role = Role.MEMBER)
|
|
||||||
test("예약을 찾을 수 없으면 예외를 던진다.") {
|
|
||||||
every {
|
|
||||||
reservationRepository.findByIdOrNull(reservationId)
|
|
||||||
} returns null
|
|
||||||
|
|
||||||
shouldThrow<ReservationException> {
|
|
||||||
reservationService.deleteWaiting(reservationId, member.id!!)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe ReservationErrorCode.RESERVATION_NOT_FOUND
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test("대기중인 해당 예약이 이미 확정된 상태라면 예외를 던진다.") {
|
|
||||||
val alreadyConfirmed = ReservationFixture.create(
|
|
||||||
id = reservationId,
|
|
||||||
status = ReservationStatus.CONFIRMED
|
|
||||||
)
|
|
||||||
every {
|
|
||||||
reservationRepository.findByIdOrNull(reservationId)
|
|
||||||
} returns alreadyConfirmed
|
|
||||||
|
|
||||||
shouldThrow<ReservationException> {
|
|
||||||
reservationService.deleteWaiting(reservationId, member.id!!)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe ReservationErrorCode.ALREADY_CONFIRMED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test("타인의 대기를 취소하려고 하면 예외를 던진다.") {
|
|
||||||
val otherMembersWaiting = ReservationFixture.create(
|
|
||||||
id = reservationId,
|
|
||||||
member = MemberFixture.create(id = member.id!! + 1L),
|
|
||||||
status = ReservationStatus.WAITING
|
|
||||||
)
|
|
||||||
|
|
||||||
every {
|
|
||||||
reservationRepository.findByIdOrNull(reservationId)
|
|
||||||
} returns otherMembersWaiting
|
|
||||||
|
|
||||||
shouldThrow<ReservationException> {
|
|
||||||
reservationService.deleteWaiting(reservationId, member.id!!)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe ReservationErrorCode.NOT_RESERVATION_OWNER
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("예약을 조회할 때") {
|
|
||||||
test("종료 날짜가 시작 날짜보다 이전이면 예외를 던진다.") {
|
|
||||||
val startFrom = LocalDate.now()
|
|
||||||
val endAt = startFrom.minusDays(1)
|
|
||||||
|
|
||||||
shouldThrow<ReservationException> {
|
|
||||||
reservationService.searchReservations(
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
startFrom,
|
|
||||||
endAt
|
|
||||||
)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe ReservationErrorCode.INVALID_SEARCH_DATE_RANGE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("대기중인 예약을 승인할 때") {
|
|
||||||
test("관리자가 아니면 예외를 던진다.") {
|
|
||||||
val member = MemberFixture.create(id = 1L, role = Role.MEMBER)
|
|
||||||
|
|
||||||
every {
|
|
||||||
memberService.findById(any())
|
|
||||||
} returns member
|
|
||||||
|
|
||||||
shouldThrow<ReservationException> {
|
|
||||||
reservationService.confirmWaiting(1L, member.id!!)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe ReservationErrorCode.NO_PERMISSION
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test("이미 확정된 예약이 있으면 예외를 던진다.") {
|
|
||||||
val member = MemberFixture.create(id = 1L, role = Role.ADMIN)
|
|
||||||
val reservationId = 1L
|
|
||||||
|
|
||||||
every {
|
|
||||||
memberService.findById(any())
|
|
||||||
} returns member
|
|
||||||
|
|
||||||
every {
|
|
||||||
reservationRepository.isExistConfirmedReservation(reservationId)
|
|
||||||
} returns true
|
|
||||||
|
|
||||||
shouldThrow<ReservationException> {
|
|
||||||
reservationService.confirmWaiting(reservationId, member.id!!)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe ReservationErrorCode.CONFIRMED_RESERVATION_ALREADY_EXISTS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("대기중인 예약을 거절할 때") {
|
|
||||||
test("관리자가 아니면 예외를 던진다.") {
|
|
||||||
val member = MemberFixture.create(id = 1L, role = Role.MEMBER)
|
|
||||||
|
|
||||||
every {
|
|
||||||
memberService.findById(any())
|
|
||||||
} returns member
|
|
||||||
|
|
||||||
shouldThrow<ReservationException> {
|
|
||||||
reservationService.rejectWaiting(1L, member.id!!)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe ReservationErrorCode.NO_PERMISSION
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test("예약을 찾을 수 없으면 예외를 던진다.") {
|
|
||||||
val member = MemberFixture.create(id = 1L, role = Role.ADMIN)
|
|
||||||
val reservationId = 1L
|
|
||||||
|
|
||||||
every {
|
|
||||||
memberService.findById(member.id!!)
|
|
||||||
} returns member
|
|
||||||
|
|
||||||
every {
|
|
||||||
reservationRepository.findByIdOrNull(reservationId)
|
|
||||||
} returns null
|
|
||||||
|
|
||||||
shouldThrow<ReservationException> {
|
|
||||||
reservationService.rejectWaiting(reservationId, member.id!!)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe ReservationErrorCode.RESERVATION_NOT_FOUND
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test("이미 확정된 예약이면 예외를 던진다.") {
|
|
||||||
val member = MemberFixture.create(id = 1L, role = Role.ADMIN)
|
|
||||||
val reservation = ReservationFixture.create(
|
|
||||||
id = 1L,
|
|
||||||
status = ReservationStatus.CONFIRMED
|
|
||||||
)
|
|
||||||
|
|
||||||
every {
|
|
||||||
memberService.findById(member.id!!)
|
|
||||||
} returns member
|
|
||||||
|
|
||||||
every {
|
|
||||||
reservationRepository.findByIdOrNull(reservation.id!!)
|
|
||||||
} returns reservation
|
|
||||||
|
|
||||||
shouldThrow<ReservationException> {
|
|
||||||
reservationService.rejectWaiting(reservation.id!!, member.id!!)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe ReservationErrorCode.ALREADY_CONFIRMED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
Loading…
x
Reference in New Issue
Block a user