generated from pricelees/issue-pr-template
[#30] 코드 구조 개선 #31
@ -2,12 +2,16 @@ package roomescape.reservation.implement
|
|||||||
|
|
||||||
import io.github.oshai.kotlinlogging.KLogger
|
import io.github.oshai.kotlinlogging.KLogger
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import org.springframework.data.jpa.domain.Specification
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import roomescape.reservation.exception.ReservationErrorCode
|
import roomescape.reservation.exception.ReservationErrorCode
|
||||||
import roomescape.reservation.exception.ReservationException
|
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.ReservationStatus
|
||||||
|
import roomescape.reservation.web.MyReservationRetrieveResponse
|
||||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
import roomescape.time.infrastructure.persistence.TimeEntity
|
import roomescape.time.infrastructure.persistence.TimeEntity
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
@ -16,7 +20,8 @@ private val log: KLogger = KotlinLogging.logger {}
|
|||||||
|
|
||||||
@Component
|
@Component
|
||||||
class ReservationFinder(
|
class ReservationFinder(
|
||||||
private val reservationRepository: ReservationRepository
|
private val reservationRepository: ReservationRepository,
|
||||||
|
private val reservationValidator: ReservationValidator,
|
||||||
) {
|
) {
|
||||||
fun findById(id: Long): ReservationEntity {
|
fun findById(id: Long): ReservationEntity {
|
||||||
log.debug { "[ReservationFinder.findById] 시작: id=$id" }
|
log.debug { "[ReservationFinder.findById] 시작: id=$id" }
|
||||||
@ -29,11 +34,15 @@ class ReservationFinder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isTimeReserved(time: TimeEntity): Boolean {
|
fun findAllByStatuses(vararg statuses: ReservationStatus): List<ReservationEntity> {
|
||||||
log.debug { "[ReservationFinder.isTimeReserved] 시작: timeId=${time.id}, startAt=${time.startAt}" }
|
log.debug { "[ReservationFinder.findAll] 시작: status=${statuses}" }
|
||||||
|
|
||||||
return reservationRepository.existsByTime(time)
|
val spec = ReservationSearchSpecification()
|
||||||
.also { log.debug { "[ReservationFinder.isTimeReserved] 완료: isExist=$it, timeId=${time.id}, startAt=${time.startAt}" } }
|
.status(*statuses)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return reservationRepository.findAll(spec)
|
||||||
|
.also { log.debug { "[ReservationFinder.findAll] ${it.size}개 예약 조회 완료: status=${statuses}" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findAllByDateAndTheme(
|
fun findAllByDateAndTheme(
|
||||||
@ -44,4 +53,42 @@ class ReservationFinder(
|
|||||||
return reservationRepository.findAllByDateAndTheme(date, theme)
|
return reservationRepository.findAllByDateAndTheme(date, theme)
|
||||||
.also { log.debug { "[ReservationFinder.findAllByDateAndTheme] ${it.size}개 조회 완료: date=$date, themeId=${theme.id}" } }
|
.also { log.debug { "[ReservationFinder.findAllByDateAndTheme] ${it.size}개 조회 완료: date=$date, themeId=${theme.id}" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun findAllByMemberId(memberId: Long): List<MyReservationRetrieveResponse> {
|
||||||
|
log.debug { "[ReservationFinder.findAllByMemberId] 시작: memberId=${memberId}" }
|
||||||
|
|
||||||
|
return reservationRepository.findAllByMemberId(memberId)
|
||||||
|
.also { log.debug { "[ReservationFinder.findAllByMemberId] ${it.size}개 예약(대기) 조회 완료: memberId=${memberId}" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchReservations(
|
||||||
|
themeId: Long?,
|
||||||
|
memberId: Long?,
|
||||||
|
startFrom: LocalDate?,
|
||||||
|
endAt: LocalDate?,
|
||||||
|
): List<ReservationEntity> {
|
||||||
|
reservationValidator.validateSearchDateRange(startFrom, endAt)
|
||||||
|
|
||||||
|
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
||||||
|
.sameThemeId(themeId)
|
||||||
|
.sameMemberId(memberId)
|
||||||
|
.dateStartFrom(startFrom)
|
||||||
|
.dateEndAt(endAt)
|
||||||
|
.status(ReservationStatus.CONFIRMED, ReservationStatus.CONFIRMED_PAYMENT_REQUIRED)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return reservationRepository.findAll(spec)
|
||||||
|
.also {
|
||||||
|
log.debug { "[ReservationFinder.searchReservations] ${it.size}개 예약 조회 완료. " +
|
||||||
|
"themeId=${themeId}, memberId=${memberId}, startFrom=${startFrom}, endAt=${endAt}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isTimeReserved(time: TimeEntity): Boolean {
|
||||||
|
log.debug { "[ReservationFinder.isTimeReserved] 시작: timeId=${time.id}, startAt=${time.startAt}" }
|
||||||
|
|
||||||
|
return reservationRepository.existsByTime(time)
|
||||||
|
.also { log.debug { "[ReservationFinder.isTimeReserved] 완료: isExist=$it, timeId=${time.id}, startAt=${time.startAt}" } }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,132 @@
|
|||||||
package roomescape.reservation.implement
|
package roomescape.reservation.implement
|
||||||
|
|
||||||
|
import io.github.oshai.kotlinlogging.KLogger
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import org.springframework.data.jpa.domain.Specification
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
|
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.ReservationRepository
|
||||||
|
import roomescape.reservation.infrastructure.persistence.ReservationSearchSpecification
|
||||||
|
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
|
import roomescape.time.infrastructure.persistence.TimeEntity
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.LocalTime
|
||||||
|
|
||||||
|
private val log: KLogger = KotlinLogging.logger {}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class ReservationValidator(
|
class ReservationValidator(
|
||||||
private val reservationRepository: ReservationRepository
|
private val reservationRepository: ReservationRepository,
|
||||||
) {
|
) {
|
||||||
|
fun validateIsNotPast(
|
||||||
|
requestDate: LocalDate,
|
||||||
|
requestTime: LocalTime,
|
||||||
|
) {
|
||||||
|
val now = LocalDateTime.now()
|
||||||
|
val requestDateTime = LocalDateTime.of(requestDate, requestTime)
|
||||||
|
log.debug { "[ReservationValidator.validateDateAndTime] 시작. request=$requestDateTime, now=$now" }
|
||||||
|
|
||||||
|
if (requestDateTime.isBefore(now)) {
|
||||||
|
log.info { "[ReservationValidator.validateDateAndTime] 날짜 범위 오류. request=$requestDateTime, now=$now" }
|
||||||
|
throw ReservationException(ReservationErrorCode.PAST_REQUEST_DATETIME)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug { "[ReservationValidator.validateDateAndTime] 완료. request=$requestDateTime, now=$now" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validateSearchDateRange(startFrom: LocalDate?, endAt: LocalDate?) {
|
||||||
|
log.debug { "[ReservationValidator.validateSearchDateRange] 시작: startFrom=$startFrom, endAt=$endAt" }
|
||||||
|
if (startFrom == null || endAt == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (startFrom.isAfter(endAt)) {
|
||||||
|
log.info { "[ReservationValidator.validateSearchDateRange] 날짜 범위 오류: startFrom=$startFrom, endAt=$endAt" }
|
||||||
|
throw ReservationException(ReservationErrorCode.INVALID_SEARCH_DATE_RANGE)
|
||||||
|
}
|
||||||
|
log.debug { "[ReservationValidator.validateSearchDateRange] 완료: startFrom=$startFrom, endAt=$endAt" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validateIsAlreadyExists(date: LocalDate, time: TimeEntity, theme: ThemeEntity) {
|
||||||
|
val themeId = theme.id
|
||||||
|
val timeId = time.id
|
||||||
|
|
||||||
|
log.debug { "[ReservationValidator.validateIsAlreadyExists] 시작: date=$date, timeId=$timeId, themeId=$themeId" }
|
||||||
|
|
||||||
|
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
||||||
|
.sameThemeId(themeId)
|
||||||
|
.sameTimeId(timeId)
|
||||||
|
.sameDate(date)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
if (reservationRepository.exists(spec)) {
|
||||||
|
log.warn { "[ReservationValidator.validateIsAlreadyExists] 중복된 예약 존재: date=$date, timeId=$timeId, themeId=$themeId" }
|
||||||
|
throw ReservationException(ReservationErrorCode.RESERVATION_DUPLICATED)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug { "[ReservationValidator.validateIsAlreadyExists] 완료: date=$date, timeId=$timeId, themeId=$themeId" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validateMemberAlreadyReserve(themeId: Long, timeId: Long, date: LocalDate, requesterId: Long) {
|
||||||
|
log.debug { "[ReservationValidator.validateMemberAlreadyReserve] 시작: themeId=$themeId, timeId=$timeId, date=$date, requesterId=$requesterId" }
|
||||||
|
|
||||||
|
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
||||||
|
.sameMemberId(requesterId)
|
||||||
|
.sameThemeId(themeId)
|
||||||
|
.sameTimeId(timeId)
|
||||||
|
.sameDate(date)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
if (reservationRepository.exists(spec)) {
|
||||||
|
log.warn { "[ReservationValidator.validateMemberAlreadyReserve] 중복된 예약 존재: themeId=$themeId, timeId=$timeId, date=$date" }
|
||||||
|
throw ReservationException(ReservationErrorCode.ALREADY_RESERVE)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug { "[ReservationValidator.validateMemberAlreadyReserve] 완료: themeId=$themeId, timeId=$timeId, date=$date, requesterId=$requesterId" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validateIsWaiting(reservation: ReservationEntity) {
|
||||||
|
log.debug { "[ReservationValidator.validateIsWaiting] 시작: reservationId=${reservation.id}, status=${reservation.status}" }
|
||||||
|
|
||||||
|
if (!reservation.isWaiting()) {
|
||||||
|
log.warn { "[ReservationValidator.validateIsWaiting] 대기 상태가 아님: reservationId=${reservation.id}, status=${reservation.status}" }
|
||||||
|
throw ReservationException(ReservationErrorCode.ALREADY_CONFIRMED)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug { "[ReservationValidator.validateIsWaiting] 완료: reservationId=${reservation.id}, status=${reservation.status}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validateCreateAuthority(requester: MemberEntity) {
|
||||||
|
log.debug { "[ReservationValidator.validateCreateAuthority] 시작: requesterId=${requester.id}" }
|
||||||
|
|
||||||
|
if (!requester.isAdmin()) {
|
||||||
|
log.error { "[ReservationValidator.validateCreateAuthority] 관리자가 아닌 다른 회원의 예약 시도: requesterId=${requester.id}" }
|
||||||
|
throw ReservationException(ReservationErrorCode.NO_PERMISSION)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug { "[ReservationValidator.validateCreateAuthority] 완료: requesterId=${requester.id}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validateDeleteAuthority(reservation: ReservationEntity, requester: MemberEntity) {
|
||||||
|
val requesterId: Long = requester.id!!
|
||||||
|
log.debug { "[ReservationValidator.validateDeleteAuthority] 시작: reservationId=${reservation.id}, requesterId=${requesterId}" }
|
||||||
|
|
||||||
|
if (requester.isAdmin()) {
|
||||||
|
log.debug { "[ReservationValidator.validateDeleteAuthority] 완료: reservationId=${reservation.id} requesterId=${requesterId}(Admin)" }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!reservation.isReservedBy(requesterId)) {
|
||||||
|
log.error {
|
||||||
|
"[ReservationValidator.validateDeleteAuthority] 예약자 본인이 아님: reservationId=${reservation.id}" +
|
||||||
|
", memberId=${reservation.member.id} requesterId=${requesterId} "
|
||||||
|
}
|
||||||
|
throw ReservationException(ReservationErrorCode.NOT_RESERVATION_OWNER)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug { "[ReservationValidator.validateDeleteAuthority] 완료: reservationId=${reservation.id}, requesterId=$requesterId" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,109 @@
|
|||||||
|
package roomescape.reservation.implement
|
||||||
|
|
||||||
|
import com.github.f4b6a3.tsid.TsidFactory
|
||||||
|
import io.github.oshai.kotlinlogging.KLogger
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import roomescape.common.config.next
|
||||||
|
import roomescape.member.implement.MemberFinder
|
||||||
|
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.ReservationStatus
|
||||||
|
import roomescape.theme.implement.ThemeFinder
|
||||||
|
import roomescape.time.implement.TimeFinder
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
private val log: KLogger = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class ReservationWriter(
|
||||||
|
private val reservationValidator: ReservationValidator,
|
||||||
|
private val reservationRepository: ReservationRepository,
|
||||||
|
private val memberFinder: MemberFinder,
|
||||||
|
private val timeFinder: TimeFinder,
|
||||||
|
private val themeFinder: ThemeFinder,
|
||||||
|
private val tsidFactory: TsidFactory,
|
||||||
|
) {
|
||||||
|
fun create(
|
||||||
|
date: LocalDate,
|
||||||
|
timeId: Long,
|
||||||
|
themeId: Long,
|
||||||
|
memberId: Long,
|
||||||
|
status: ReservationStatus,
|
||||||
|
requesterId: Long
|
||||||
|
): ReservationEntity {
|
||||||
|
log.debug {
|
||||||
|
"[ReservationWriter.create] 시작: " +
|
||||||
|
"date=${date}, timeId=${timeId}, themeId=${themeId}, memberId=${memberId}, status=${status}"
|
||||||
|
}
|
||||||
|
val time = timeFinder.findById(timeId).also {
|
||||||
|
reservationValidator.validateIsNotPast(date, it.startAt)
|
||||||
|
}
|
||||||
|
val theme = themeFinder.findById(themeId)
|
||||||
|
|
||||||
|
val member = memberFinder.findById(memberId).also {
|
||||||
|
if (status == ReservationStatus.WAITING) {
|
||||||
|
reservationValidator.validateMemberAlreadyReserve(themeId, timeId, date, it.id!!)
|
||||||
|
} else {
|
||||||
|
reservationValidator.validateIsAlreadyExists(date, time, theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memberId != requesterId) {
|
||||||
|
val requester = memberFinder.findById(requesterId)
|
||||||
|
reservationValidator.validateCreateAuthority(requester)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val reservation = ReservationEntity(
|
||||||
|
_id = tsidFactory.next(),
|
||||||
|
date = date,
|
||||||
|
time = time,
|
||||||
|
theme = theme,
|
||||||
|
member = member,
|
||||||
|
status = status
|
||||||
|
)
|
||||||
|
|
||||||
|
return reservationRepository.save(reservation)
|
||||||
|
.also { log.debug { "[ReservationWriter.create] 완료: reservationId=${it.id}, status=${it.status}" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteWaiting(reservation: ReservationEntity, requesterId: Long) {
|
||||||
|
log.debug { "[ReservationWriter.deleteWaiting] 시작: reservationId=${reservation.id}, requesterId=${requesterId}" }
|
||||||
|
|
||||||
|
reservationValidator.validateIsWaiting(reservation)
|
||||||
|
|
||||||
|
delete(reservation, requesterId)
|
||||||
|
.also { log.debug { "[ReservationWriter.deleteWaiting] 완료: reservationId=${reservation.id}, status=${reservation.status}" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteConfirmed(reservation: ReservationEntity, requesterId: Long) {
|
||||||
|
log.debug { "[ReservationWriter.deleteConfirmed] 시작: reservationId=${reservation.id}, requesterId=${requesterId}" }
|
||||||
|
|
||||||
|
delete(reservation, requesterId)
|
||||||
|
.also { log.debug { "[ReservationWriter.deleteConfirmed] 완료: reservationId=${reservation.id}, status=${reservation.status}" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun delete(reservation: ReservationEntity, requesterId: Long) {
|
||||||
|
memberFinder.findById(requesterId)
|
||||||
|
.also { reservationValidator.validateDeleteAuthority(reservation, requester = it) }
|
||||||
|
|
||||||
|
reservationRepository.delete(reservation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun confirm(reservationId: Long) {
|
||||||
|
log.debug { "[ReservationWriter.confirm] 대기 여부 확인 시작: reservationId=$reservationId" }
|
||||||
|
|
||||||
|
if (reservationRepository.isExistConfirmedReservation(reservationId)) {
|
||||||
|
log.warn { "[ReservationWriter.confirm] 이미 확정된 예약 존재: reservationId=$reservationId" }
|
||||||
|
throw ReservationException(ReservationErrorCode.CONFIRMED_RESERVATION_ALREADY_EXISTS)
|
||||||
|
}
|
||||||
|
|
||||||
|
reservationRepository.updateStatusByReservationId(
|
||||||
|
reservationId, ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
||||||
|
)
|
||||||
|
|
||||||
|
log.debug { "[ReservationWriter.confirm] 완료: reservationId=$reservationId, status=${ReservationStatus.CONFIRMED_PAYMENT_REQUIRED}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user