feat: Schedule 상태 변경시 holdExpiredAt 처리 추가 및 기존 코드 반영 & 테스트

This commit is contained in:
이상진 2025-10-02 15:43:11 +09:00
parent a8ed0de625
commit 459fb331ae
4 changed files with 64 additions and 16 deletions

View File

@ -11,7 +11,6 @@ import com.sangdol.roomescape.reservation.web.*
import com.sangdol.roomescape.schedule.business.ScheduleService import com.sangdol.roomescape.schedule.business.ScheduleService
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
import com.sangdol.roomescape.schedule.web.ScheduleOverviewResponse import com.sangdol.roomescape.schedule.web.ScheduleOverviewResponse
import com.sangdol.roomescape.schedule.web.ScheduleUpdateRequest
import com.sangdol.roomescape.theme.business.ThemeService import com.sangdol.roomescape.theme.business.ThemeService
import com.sangdol.roomescape.user.business.UserService import com.sangdol.roomescape.user.business.UserService
import com.sangdol.roomescape.user.web.UserContactResponse import com.sangdol.roomescape.user.web.UserContactResponse
@ -58,9 +57,10 @@ class ReservationService(
run { run {
reservation.confirm() reservation.confirm()
scheduleService.updateSchedule( scheduleService.changeStatus(
reservation.scheduleId, scheduleId = reservation.scheduleId,
ScheduleUpdateRequest(status = ScheduleStatus.RESERVED) currentStatus = ScheduleStatus.HOLD,
changeStatus = ScheduleStatus.RESERVED
) )
}.also { }.also {
log.info { "[ReservationService.confirmReservation] Pending 예약 확정 완료: reservationId=${id}" } log.info { "[ReservationService.confirmReservation] Pending 예약 확정 완료: reservationId=${id}" }
@ -74,9 +74,10 @@ class ReservationService(
val reservation: ReservationEntity = findOrThrow(reservationId) val reservation: ReservationEntity = findOrThrow(reservationId)
run { run {
scheduleService.updateSchedule( scheduleService.changeStatus(
reservation.scheduleId, scheduleId = reservation.scheduleId,
ScheduleUpdateRequest(status = ScheduleStatus.AVAILABLE) currentStatus = ScheduleStatus.RESERVED,
changeStatus = ScheduleStatus.AVAILABLE
) )
saveCanceledReservation(user, reservation, request.cancelReason) saveCanceledReservation(user, reservation, request.cancelReason)
reservation.cancel() reservation.cancel()
@ -148,6 +149,7 @@ class ReservationService(
status = CanceledReservationStatus.COMPLETED status = CanceledReservationStatus.COMPLETED
).also { ).also {
canceledReservationRepository.save(it) canceledReservationRepository.save(it)
} }
} }

View File

@ -10,6 +10,7 @@ import com.sangdol.roomescape.schedule.exception.ScheduleErrorCode
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntityFactory import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntityFactory
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
import com.sangdol.roomescape.schedule.web.* import com.sangdol.roomescape.schedule.web.*
import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
@ -66,7 +67,11 @@ class ScheduleService(
@Transactional @Transactional
fun holdSchedule(id: Long) { fun holdSchedule(id: Long) {
log.info { "[ScheduleService.holdSchedule] 일정 Holding 시작: id=$id" } log.info { "[ScheduleService.holdSchedule] 일정 Holding 시작: id=$id" }
val result: Int = scheduleRepository.holdAvailableScheduleById(id) val result: Int = scheduleRepository.changeStatus(
id = id,
currentStatus = ScheduleStatus.AVAILABLE,
changeStatus = ScheduleStatus.HOLD
)
if (result == 0) { if (result == 0) {
log.info { "[ScheduleService.holdSchedule] Holding된 일정 없음: id=${id}, count = " } log.info { "[ScheduleService.holdSchedule] Holding된 일정 없음: id=${id}, count = " }
@ -188,6 +193,15 @@ class ScheduleService(
return overview.toOverviewResponse() return overview.toOverviewResponse()
} }
@Transactional
fun changeStatus(scheduleId: Long, currentStatus: ScheduleStatus, changeStatus: ScheduleStatus) {
log.info { "[ScheduleService.reserveSchedule] 일정 상태 변경 시작: id=${scheduleId}, currentStatus=${currentStatus}, changeStatus=${changeStatus}" }
scheduleRepository.changeStatus(scheduleId, currentStatus, changeStatus).also {
log.info { "[ScheduleService.reserveSchedule] 일정 상태 변경 완료: id=${scheduleId}, currentStatus=${currentStatus}, changeStatus=${changeStatus}" }
}
}
// ======================================== // ========================================
// Common (공통 메서드) // Common (공통 메서드)
// ======================================== // ========================================

View File

@ -1,9 +1,9 @@
package com.sangdol.roomescape.schedule.infrastructure.persistence package com.sangdol.roomescape.schedule.infrastructure.persistence
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import com.sangdol.roomescape.schedule.business.domain.ScheduleOverview import com.sangdol.roomescape.schedule.business.domain.ScheduleOverview
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
@ -85,15 +85,22 @@ interface ScheduleRepository : JpaRepository<ScheduleEntity, Long> {
fun findOverviewByIdOrNull(id: Long): ScheduleOverview? fun findOverviewByIdOrNull(id: Long): ScheduleOverview?
@Modifying @Modifying
@Query(""" @Query(
"""
UPDATE UPDATE
ScheduleEntity s ScheduleEntity s
SET SET
s.status = 'HOLD' s.status = :changeStatus,
s.holdExpiredAt = CASE
WHEN :changeStatus = com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus.HOLD
THEN CURRENT_TIMESTAMP + 5 MINUTE
ELSE NULL
END
WHERE WHERE
s._id = :id s._id = :id
AND AND
s.status = 'AVAILABLE' s.status = :currentStatus
""") """
fun holdAvailableScheduleById(id: Long): Int )
fun changeStatus(id: Long, currentStatus: ScheduleStatus, changeStatus: ScheduleStatus): Int
} }

View File

@ -10,6 +10,7 @@ import com.sangdol.roomescape.supports.FunSpecSpringbootTest
import com.sangdol.roomescape.supports.IDGenerator import com.sangdol.roomescape.supports.IDGenerator
import com.sangdol.roomescape.supports.initialize import com.sangdol.roomescape.supports.initialize
import io.kotest.assertions.assertSoftly import io.kotest.assertions.assertSoftly
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe import io.kotest.matchers.shouldNotBe
@ -48,7 +49,7 @@ class ScheduleServiceTest(
} }
} }
test("유저가 일정을 Holding 하는 경우에는 updatedAt, updatedBy 컬럼이 변경되지 않는다.") { test("유저가 일정을 Holding 상태로 변경하는 경우에는 holdExpiredAt 컬럼이 5분 뒤로 지정되며, updatedAt, updatedBy 컬럼이 변경되지 않는다.") {
val createdScheduleId: Long = createSchedule() val createdScheduleId: Long = createSchedule()
initialize("updatedBy 변경 확인을 위한 MDC 재설정") { initialize("updatedBy 변경 확인을 위한 MDC 재설정") {
@ -64,6 +65,30 @@ class ScheduleServiceTest(
this.shouldNotBeNull() this.shouldNotBeNull()
this.updatedAt shouldBe this.createdAt this.updatedAt shouldBe this.createdAt
this.updatedBy shouldBe this.createdBy this.updatedBy shouldBe this.createdBy
this.holdExpiredAt.shouldNotBeNull()
}
}
test("유저가 일정을 Holding이 아닌 다른 상태로 변경 경우에는 holdExpiredAt 컬럼이 null로 지정되며, updatedAt, updatedBy 컬럼이 변경되지 않는다.") {
val createdScheduleId: Long = createSchedule()
initialize("updatedBy 변경 확인을 위한 MDC 재설정") {
MdcPrincipalIdUtil.clear()
IDGenerator.create().also {
MdcPrincipalIdUtil.set(it.toString())
}
}
scheduleService.holdSchedule(createdScheduleId).also {
scheduleRepository.findByIdOrNull(createdScheduleId)!!.holdExpiredAt.shouldNotBeNull()
}
scheduleService.changeStatus(createdScheduleId, ScheduleStatus.HOLD, ScheduleStatus.RESERVED).also {
assertSoftly(scheduleRepository.findByIdOrNull(createdScheduleId)!!) {
this.updatedAt shouldBe this.createdAt
this.updatedBy shouldBe this.createdBy
this.holdExpiredAt.shouldBeNull()
}
} }
} }
} }