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.KotlinLogging
|
||||
import org.springframework.data.jpa.domain.Specification
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Component
|
||||
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.MyReservationRetrieveResponse
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
import roomescape.time.infrastructure.persistence.TimeEntity
|
||||
import java.time.LocalDate
|
||||
@ -16,7 +20,8 @@ private val log: KLogger = KotlinLogging.logger {}
|
||||
|
||||
@Component
|
||||
class ReservationFinder(
|
||||
private val reservationRepository: ReservationRepository
|
||||
private val reservationRepository: ReservationRepository,
|
||||
private val reservationValidator: ReservationValidator,
|
||||
) {
|
||||
fun findById(id: Long): ReservationEntity {
|
||||
log.debug { "[ReservationFinder.findById] 시작: id=$id" }
|
||||
@ -29,11 +34,15 @@ class ReservationFinder(
|
||||
}
|
||||
}
|
||||
|
||||
fun isTimeReserved(time: TimeEntity): Boolean {
|
||||
log.debug { "[ReservationFinder.isTimeReserved] 시작: timeId=${time.id}, startAt=${time.startAt}" }
|
||||
fun findAllByStatuses(vararg statuses: ReservationStatus): List<ReservationEntity> {
|
||||
log.debug { "[ReservationFinder.findAll] 시작: status=${statuses}" }
|
||||
|
||||
return reservationRepository.existsByTime(time)
|
||||
.also { log.debug { "[ReservationFinder.isTimeReserved] 완료: isExist=$it, timeId=${time.id}, startAt=${time.startAt}" } }
|
||||
val spec = ReservationSearchSpecification()
|
||||
.status(*statuses)
|
||||
.build()
|
||||
|
||||
return reservationRepository.findAll(spec)
|
||||
.also { log.debug { "[ReservationFinder.findAll] ${it.size}개 예약 조회 완료: status=${statuses}" } }
|
||||
}
|
||||
|
||||
fun findAllByDateAndTheme(
|
||||
@ -44,4 +53,42 @@ class ReservationFinder(
|
||||
return reservationRepository.findAllByDateAndTheme(date, theme)
|
||||
.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
|
||||
|
||||
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 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.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
|
||||
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