generated from pricelees/issue-pr-template
feat: time 도메인 Finder, Writer, Validator 도입 및 Service 코드 수정
This commit is contained in:
parent
6cac32ffb2
commit
6d56f035c3
@ -0,0 +1,34 @@
|
||||
package roomescape.reservation.implement
|
||||
|
||||
import io.github.oshai.kotlinlogging.KLogger
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.springframework.stereotype.Component
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
import roomescape.time.infrastructure.persistence.TimeEntity
|
||||
import java.time.LocalDate
|
||||
|
||||
private val log: KLogger = KotlinLogging.logger {}
|
||||
|
||||
@Component
|
||||
class ReservationFinder(
|
||||
private val reservationRepository: ReservationRepository
|
||||
) {
|
||||
|
||||
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}" } }
|
||||
}
|
||||
|
||||
fun findAllByDateAndTheme(
|
||||
date: LocalDate, theme: ThemeEntity
|
||||
): List<ReservationEntity> {
|
||||
log.debug { "[ReservationFinder.findAllByDateAndTheme] 시작: date=$date, themeId=${theme.id}" }
|
||||
|
||||
return reservationRepository.findAllByDateAndTheme(date, theme)
|
||||
.also { log.debug { "[ReservationFinder.findAllByDateAndTheme] ${it.size}개 조회 완료: date=$date, themeId=${theme.id}" } }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package roomescape.reservation.implement
|
||||
|
||||
import org.springframework.stereotype.Component
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||
|
||||
@Component
|
||||
class ReservationValidator(
|
||||
private val reservationRepository: ReservationRepository
|
||||
) {
|
||||
|
||||
}
|
||||
@ -6,12 +6,14 @@ import org.springframework.data.jpa.repository.Modifying
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.query.Param
|
||||
import roomescape.reservation.web.MyReservationRetrieveResponse
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
import roomescape.time.infrastructure.persistence.TimeEntity
|
||||
import java.time.LocalDate
|
||||
|
||||
interface ReservationRepository
|
||||
: JpaRepository<ReservationEntity, Long>, JpaSpecificationExecutor<ReservationEntity> {
|
||||
fun findAllByTime(time: TimeEntity): List<ReservationEntity>
|
||||
fun existsByTime(time: TimeEntity): Boolean
|
||||
|
||||
fun findByDateAndThemeId(date: LocalDate, themeId: Long): List<ReservationEntity>
|
||||
|
||||
@ -20,11 +22,11 @@ interface ReservationRepository
|
||||
"""
|
||||
UPDATE ReservationEntity r
|
||||
SET r.status = :status
|
||||
WHERE r.id = :id
|
||||
WHERE r._id = :_id
|
||||
"""
|
||||
)
|
||||
fun updateStatusByReservationId(
|
||||
@Param(value = "id") reservationId: Long,
|
||||
@Param(value = "_id") reservationId: Long,
|
||||
@Param(value = "status") statusForChange: ReservationStatus
|
||||
): Int
|
||||
|
||||
@ -33,28 +35,28 @@ interface ReservationRepository
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM ReservationEntity r2
|
||||
WHERE r2.id = :id
|
||||
WHERE r2._id = :_id
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM ReservationEntity r
|
||||
WHERE r.theme.id = r2.theme.id
|
||||
AND r.time.id = r2.time.id
|
||||
WHERE r.theme._id = r2.theme._id
|
||||
AND r.time._id = r2.time._id
|
||||
AND r.date = r2.date
|
||||
AND r.status != 'WAITING'
|
||||
)
|
||||
)
|
||||
"""
|
||||
)
|
||||
fun isExistConfirmedReservation(@Param("id") reservationId: Long): Boolean
|
||||
fun isExistConfirmedReservation(@Param("_id") reservationId: Long): Boolean
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT new roomescape.reservation.web.MyReservationRetrieveResponse(
|
||||
r.id,
|
||||
r._id,
|
||||
t.name,
|
||||
r.date,
|
||||
r.time.startAt,
|
||||
r.status,
|
||||
(SELECT COUNT (r2) * 1L FROM ReservationEntity r2 WHERE r2.theme = r.theme AND r2.date = r.date AND r2.time = r.time AND r2.id < r.id),
|
||||
(SELECT COUNT (r2) * 1L FROM ReservationEntity r2 WHERE r2.theme = r.theme AND r2.date = r.date AND r2.time = r.time AND r2._id < r._id),
|
||||
p.paymentKey,
|
||||
p.totalAmount
|
||||
)
|
||||
@ -62,8 +64,9 @@ interface ReservationRepository
|
||||
JOIN r.theme t
|
||||
LEFT JOIN PaymentEntity p
|
||||
ON p.reservation = r
|
||||
WHERE r.member.id = :memberId
|
||||
WHERE r.member._id = :memberId
|
||||
"""
|
||||
)
|
||||
fun findAllByMemberId(memberId: Long): List<MyReservationRetrieveResponse>
|
||||
fun findAllByDateAndTheme(date: LocalDate, theme: ThemeEntity): List<ReservationEntity>
|
||||
}
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
package roomescape.theme.business.domain
|
||||
|
||||
import roomescape.time.web.TimeWithAvailabilityResponse
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
class TimeWithAvailability(
|
||||
private val timeId: Long,
|
||||
private val startAt: LocalTime,
|
||||
private val date: LocalDate,
|
||||
private val themeId: Long,
|
||||
private val isReservable: Boolean
|
||||
) {
|
||||
fun toResponse() = TimeWithAvailabilityResponse(
|
||||
id = timeId,
|
||||
startAt = startAt,
|
||||
isAvailable = isReservable
|
||||
)
|
||||
}
|
||||
28
src/main/kotlin/roomescape/theme/implement/ThemeFinder.kt
Normal file
28
src/main/kotlin/roomescape/theme/implement/ThemeFinder.kt
Normal file
@ -0,0 +1,28 @@
|
||||
package roomescape.theme.implement
|
||||
|
||||
import io.github.oshai.kotlinlogging.KLogger
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Component
|
||||
import roomescape.theme.exception.ThemeErrorCode
|
||||
import roomescape.theme.exception.ThemeException
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
import roomescape.theme.infrastructure.persistence.ThemeRepository
|
||||
|
||||
private val log: KLogger = KotlinLogging.logger {}
|
||||
|
||||
@Component
|
||||
class ThemeFinder(
|
||||
private val themeRepository: ThemeRepository
|
||||
) {
|
||||
fun findById(id: Long): ThemeEntity {
|
||||
log.debug { "[ThemeFinder.findById] 조회 시작: memberId=$id" }
|
||||
|
||||
return themeRepository.findByIdOrNull(id)
|
||||
?.also { log.debug { "[ThemeFinder.findById] 조회 완료: id=$id, name=${it.name}" } }
|
||||
?: run {
|
||||
log.warn { "[ThemeFinder.findById] 조회 실패: id=$id" }
|
||||
throw ThemeException(ThemeErrorCode.THEME_NOT_FOUND)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,12 @@
|
||||
package roomescape.time.business
|
||||
|
||||
import com.github.f4b6a3.tsid.TsidFactory
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import roomescape.common.config.next
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||
import roomescape.time.exception.TimeErrorCode
|
||||
import roomescape.time.exception.TimeException
|
||||
import roomescape.theme.business.domain.TimeWithAvailability
|
||||
import roomescape.time.implement.TimeFinder
|
||||
import roomescape.time.implement.TimeWriter
|
||||
import roomescape.time.infrastructure.persistence.TimeEntity
|
||||
import roomescape.time.infrastructure.persistence.TimeRepository
|
||||
import roomescape.time.web.*
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
@ -20,87 +15,53 @@ private val log = KotlinLogging.logger {}
|
||||
|
||||
@Service
|
||||
class TimeService(
|
||||
private val tsidFactory: TsidFactory,
|
||||
private val timeRepository: TimeRepository,
|
||||
private val reservationRepository: ReservationRepository,
|
||||
private val timeFinder: TimeFinder,
|
||||
private val timeWriter: TimeWriter,
|
||||
) {
|
||||
@Transactional(readOnly = true)
|
||||
fun findById(id: Long): TimeEntity {
|
||||
log.debug { "[TimeService.findById] 시간 조회 시작: timeId=$id" }
|
||||
return timeRepository.findByIdOrNull(id)
|
||||
?.also { log.info { "[TimeService.findById] 시간 조회 완료: timeId=$id" } }
|
||||
?: run {
|
||||
log.warn { "[TimeService.findById] 시간 조회 실패: timeId=$id" }
|
||||
throw TimeException(TimeErrorCode.TIME_NOT_FOUND)
|
||||
}
|
||||
log.info { "[TimeService.findById] 시작: timeId=$id" }
|
||||
|
||||
return timeFinder.findById(id)
|
||||
.also { log.info { "[TimeService.findById] 완료: timeId=$id, startAt=${it.startAt}" } }
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun findTimes(): TimeRetrieveListResponse {
|
||||
log.debug { "[TimeService.findTimes] 모든 시간 조회 시작" }
|
||||
return timeRepository.findAll()
|
||||
.also { log.info { "[TimeService.findTimes] ${it.size}개의 시간 조회 완료" } }
|
||||
log.info { "[TimeService.findTimes] 시작" }
|
||||
|
||||
return timeFinder.findAll()
|
||||
.toResponse()
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun createTime(request: TimeCreateRequest): TimeCreateResponse {
|
||||
log.debug { "[TimeService.createTime] 시간 생성 시작: startAt=${request.startAt}" }
|
||||
|
||||
val startAt: LocalTime = request.startAt
|
||||
if (timeRepository.existsByStartAt(startAt)) {
|
||||
log.info { "[TimeService.createTime] 시간 생성 실패(시간 중복): startAt=$startAt" }
|
||||
throw TimeException(TimeErrorCode.TIME_DUPLICATED)
|
||||
}
|
||||
|
||||
val time = TimeEntity(
|
||||
_id = tsidFactory.next(),
|
||||
startAt = request.startAt
|
||||
)
|
||||
return timeRepository.save(time)
|
||||
.also { log.info { "[TimeService.createTime] 시간 생성 완료: timeId=${it.id}" } }
|
||||
.toCreateResponse()
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun deleteTime(id: Long) {
|
||||
log.debug { "[TimeService.deleteTime] 시간 삭제 시작: timeId=$id" }
|
||||
|
||||
val time: TimeEntity = findById(id)
|
||||
|
||||
log.debug { "[TimeService.deleteTime] 시간이 ${time.startAt}인 모든 예약 조회 시작" }
|
||||
val reservations: List<ReservationEntity> = reservationRepository.findAllByTime(time)
|
||||
log.debug { "[TimeService.deleteTime] 시간이 ${time.startAt}인 모든 ${reservations.size} 개의 예약 조회 완료" }
|
||||
|
||||
if (reservations.isNotEmpty()) {
|
||||
log.info { "[TimeService.deleteTime] 시간 삭제 실패(예약이 있는 시간): timeId=$id" }
|
||||
throw TimeException(TimeErrorCode.TIME_ALREADY_RESERVED)
|
||||
}
|
||||
|
||||
timeRepository.delete(time)
|
||||
.also { log.info { "[TimeService.deleteTime] 시간 삭제 완료: timeId=$id" } }
|
||||
.also { log.info { "[TimeService.findTimes] 완료. ${it.times.size}개 반환" } }
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun findTimesWithAvailability(date: LocalDate, themeId: Long): TimeWithAvailabilityListResponse {
|
||||
log.debug { "[TimeService.findTimesWithAvailability] 예약 가능 시간 조회 시작: date=$date, themeId=$themeId" }
|
||||
log.info { "[TimeService.findTimesWithAvailability] 시작: date=$date, themeId=$themeId" }
|
||||
|
||||
log.debug { "[TimeService.findTimesWithAvailability] 모든 시간 조회 " }
|
||||
val allTimes = timeRepository.findAll()
|
||||
log.debug { "[TimeService.findTimesWithAvailability] ${allTimes.size}개의 시간 조회 완료" }
|
||||
val times: List<TimeWithAvailability> = timeFinder.findAllWithAvailabilityByDateAndThemeId(date, themeId)
|
||||
|
||||
log.debug { "[TimeService.findTimesWithAvailability] date=$date, themeId=$themeId 인 모든 예약 조회 시작" }
|
||||
val reservations: List<ReservationEntity> = reservationRepository.findByDateAndThemeId(date, themeId)
|
||||
log.debug { "[TimeService.findTimesWithAvailability] date=$date, themeId=$themeId 인 ${reservations.size} 개의 예약 조회 완료" }
|
||||
return TimeWithAvailabilityListResponse(times.map { it.toResponse() })
|
||||
.also { log.info { "[TimeService.findTimesWithAvailability] ${it.times.size}개 반환: date=$date, themeId=$themeId" } }
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun createTime(request: TimeCreateRequest): TimeCreateResponse {
|
||||
val startAt: LocalTime = request.startAt
|
||||
log.info { "[TimeService.createTime] 시작: startAt=${startAt}" }
|
||||
|
||||
return TimeWithAvailabilityListResponse(allTimes.map { time ->
|
||||
val isAvailable: Boolean = reservations.none { reservation -> reservation.time.id == time.id }
|
||||
TimeWithAvailabilityResponse(time.id!!, time.startAt, isAvailable)
|
||||
}).also {
|
||||
log.info {
|
||||
"[TimeService.findTimesWithAvailability] date=$date, themeId=$themeId 에 대한 예약 가능 여부가 담긴 모든 시간 조회 완료"
|
||||
}
|
||||
}
|
||||
return timeWriter.create(startAt)
|
||||
.toCreateResponse()
|
||||
.also { log.info { "[TimeService.createTime] 완료: startAt=${startAt}, timeId=${it.id}" } }
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun deleteTime(id: Long) {
|
||||
log.info { "[TimeService.deleteTime] 시작: timeId=$id" }
|
||||
|
||||
val time: TimeEntity = timeFinder.findById(id)
|
||||
|
||||
timeWriter.delete(time)
|
||||
.also { log.info { "[TimeService.deleteTime] 완료: timeId=$id, startAt=${time.startAt}" } }
|
||||
}
|
||||
}
|
||||
|
||||
60
src/main/kotlin/roomescape/time/implement/TimeFinder.kt
Normal file
60
src/main/kotlin/roomescape/time/implement/TimeFinder.kt
Normal file
@ -0,0 +1,60 @@
|
||||
package roomescape.time.implement
|
||||
|
||||
import io.github.oshai.kotlinlogging.KLogger
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Component
|
||||
import roomescape.reservation.implement.ReservationFinder
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.theme.business.domain.TimeWithAvailability
|
||||
import roomescape.theme.implement.ThemeFinder
|
||||
import roomescape.time.exception.TimeErrorCode
|
||||
import roomescape.time.exception.TimeException
|
||||
import roomescape.time.infrastructure.persistence.TimeEntity
|
||||
import roomescape.time.infrastructure.persistence.TimeRepository
|
||||
import java.time.LocalDate
|
||||
|
||||
private val log: KLogger = KotlinLogging.logger {}
|
||||
|
||||
@Component
|
||||
class TimeFinder(
|
||||
private val timeRepository: TimeRepository,
|
||||
private val reservationFinder: ReservationFinder,
|
||||
private val themeFinder: ThemeFinder
|
||||
) {
|
||||
|
||||
fun findAll(): List<TimeEntity> {
|
||||
log.debug { "[TimeFinder.findAll] 시작" }
|
||||
|
||||
return timeRepository.findAll()
|
||||
.also { log.debug { "[TimeFinder.findAll] ${it.size}개 시간 조회 완료" } }
|
||||
}
|
||||
|
||||
fun findById(id: Long): TimeEntity {
|
||||
log.debug { "[TimeFinder.findById] 조회 시작: timeId=$id" }
|
||||
|
||||
return timeRepository.findByIdOrNull(id)
|
||||
?.also { log.debug { "[TimeFinder.findById] 조회 완료: timeId=$id, startAt=${it.startAt}" } }
|
||||
?: run {
|
||||
log.warn { "[TimeFinder.findById] 조회 실패: timeId=$id" }
|
||||
throw TimeException(TimeErrorCode.TIME_NOT_FOUND)
|
||||
}
|
||||
}
|
||||
|
||||
fun findAllWithAvailabilityByDateAndThemeId(
|
||||
date: LocalDate, themeId: Long
|
||||
): List<TimeWithAvailability> {
|
||||
log.debug { "[TimeFinder.findAllWithAvailabilityByDateAndThemeId] 조회 시작: date:$date, themeId=$themeId" }
|
||||
|
||||
val theme = themeFinder.findById(themeId)
|
||||
val reservations: List<ReservationEntity> = reservationFinder.findAllByDateAndTheme(date, theme)
|
||||
val allTimes: List<TimeEntity> = findAll()
|
||||
|
||||
return allTimes.map { time ->
|
||||
val isReservable: Boolean = reservations.any { reservation -> time.id == reservation.id }
|
||||
TimeWithAvailability(time.id!!, time.startAt, date, themeId, isReservable)
|
||||
}.also {
|
||||
log.debug { "[TimeFinder.findAllWithAvailabilityByDateAndThemeId] ${it.size}개 조회 완료: date:$date, themeId=$themeId" }
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/main/kotlin/roomescape/time/implement/TimeValidator.kt
Normal file
41
src/main/kotlin/roomescape/time/implement/TimeValidator.kt
Normal file
@ -0,0 +1,41 @@
|
||||
package roomescape.time.implement
|
||||
|
||||
import io.github.oshai.kotlinlogging.KLogger
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.springframework.stereotype.Component
|
||||
import roomescape.reservation.implement.ReservationFinder
|
||||
import roomescape.time.exception.TimeErrorCode
|
||||
import roomescape.time.exception.TimeException
|
||||
import roomescape.time.infrastructure.persistence.TimeEntity
|
||||
import roomescape.time.infrastructure.persistence.TimeRepository
|
||||
import java.time.LocalTime
|
||||
|
||||
private val log: KLogger = KotlinLogging.logger {}
|
||||
|
||||
@Component
|
||||
class TimeValidator(
|
||||
private val timeRepository: TimeRepository,
|
||||
private val reservationFinder: ReservationFinder
|
||||
) {
|
||||
fun validateIsAlreadyExists(startAt: LocalTime) {
|
||||
log.debug { "[TimeValidator.validateIsAlreadyExists] 시작: startAt=${startAt}" }
|
||||
|
||||
if (timeRepository.existsByStartAt(startAt)) {
|
||||
log.info { "[TimeValidator.validateIsAlreadyExists] 중복 시간: startAt=$startAt" }
|
||||
throw TimeException(TimeErrorCode.TIME_DUPLICATED)
|
||||
}
|
||||
|
||||
log.debug { "[TimeValidator.validateIsAlreadyExists] 완료: startAt=${startAt}" }
|
||||
}
|
||||
|
||||
fun validateIsReserved(time: TimeEntity) {
|
||||
log.debug { "[TimeValidator.validateIsReserved] 시작: id=${time.id}, startAt=${time.startAt}" }
|
||||
|
||||
if (reservationFinder.isTimeReserved(time)) {
|
||||
log.info { "[TimeValidator.validateIsReserved] 예약이 있는 시간: timeId=${time.id}, startAt=${time.startAt}" }
|
||||
throw TimeException(TimeErrorCode.TIME_ALREADY_RESERVED)
|
||||
}
|
||||
|
||||
log.debug { "[TimeValidator.validateIsReserved] 시작: id=${time.id}, startAt=${time.startAt}" }
|
||||
}
|
||||
}
|
||||
37
src/main/kotlin/roomescape/time/implement/TimeWriter.kt
Normal file
37
src/main/kotlin/roomescape/time/implement/TimeWriter.kt
Normal file
@ -0,0 +1,37 @@
|
||||
package roomescape.time.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.time.infrastructure.persistence.TimeEntity
|
||||
import roomescape.time.infrastructure.persistence.TimeRepository
|
||||
import java.time.LocalTime
|
||||
|
||||
private val log: KLogger = KotlinLogging.logger {}
|
||||
|
||||
@Component
|
||||
class TimeWriter(
|
||||
private val timeValidator: TimeValidator,
|
||||
private val timeRepository: TimeRepository,
|
||||
private val tsidFactory: TsidFactory
|
||||
) {
|
||||
fun create(startAt: LocalTime): TimeEntity {
|
||||
log.debug { "[TimeWriter.create] 시작: startAt=$startAt" }
|
||||
timeValidator.validateIsAlreadyExists(startAt)
|
||||
|
||||
val time = TimeEntity(_id = tsidFactory.next(), startAt = startAt)
|
||||
|
||||
return timeRepository.save(time)
|
||||
.also { log.debug { "[TimeWriter.create] 완료: startAt=$startAt, id=${it.id}" } }
|
||||
}
|
||||
|
||||
fun delete(time: TimeEntity) {
|
||||
log.debug { "[TimeWriter.delete] 시작: id=${time.id}" }
|
||||
timeValidator.validateIsReserved(time)
|
||||
|
||||
timeRepository.delete(time)
|
||||
.also { log.debug { "[TimeWriter.delete] 완료: id=${time.id}, startAt=${time.startAt}" } }
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user