generated from pricelees/issue-pr-template
refactor: ScheduleEntity에 \@LastModifiedBy 추가 및 회원이 사용하는 hold API는 Update 쿼리를 바로 쓰도록 하여 업데이트 방지
This commit is contained in:
parent
86a2459d8b
commit
08b9920ee3
@ -2,15 +2,14 @@ package com.sangdol.roomescape.schedule.business
|
|||||||
|
|
||||||
import ScheduleException
|
import ScheduleException
|
||||||
import com.sangdol.common.persistence.IDGenerator
|
import com.sangdol.common.persistence.IDGenerator
|
||||||
|
import com.sangdol.roomescape.admin.business.AdminService
|
||||||
import com.sangdol.roomescape.common.types.AuditingInfo
|
import com.sangdol.roomescape.common.types.AuditingInfo
|
||||||
import com.sangdol.roomescape.common.types.Auditor
|
import com.sangdol.roomescape.common.types.Auditor
|
||||||
import com.sangdol.roomescape.admin.business.AdminService
|
|
||||||
import com.sangdol.roomescape.schedule.business.domain.ScheduleOverview
|
import com.sangdol.roomescape.schedule.business.domain.ScheduleOverview
|
||||||
import com.sangdol.roomescape.schedule.exception.ScheduleErrorCode
|
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
|
||||||
@ -18,7 +17,6 @@ import org.springframework.data.repository.findByIdOrNull
|
|||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
|
|
||||||
private val log: KLogger = KotlinLogging.logger {}
|
private val log: KLogger = KotlinLogging.logger {}
|
||||||
@ -67,14 +65,15 @@ class ScheduleService(
|
|||||||
// ========================================
|
// ========================================
|
||||||
@Transactional
|
@Transactional
|
||||||
fun holdSchedule(id: Long) {
|
fun holdSchedule(id: Long) {
|
||||||
val schedule: ScheduleEntity = findOrThrow(id)
|
log.info { "[ScheduleService.holdSchedule] 일정 Holding 시작: id=$id" }
|
||||||
|
val result: Int = scheduleRepository.holdAvailableScheduleById(id)
|
||||||
|
|
||||||
if (schedule.status == ScheduleStatus.AVAILABLE) {
|
if (result == 0) {
|
||||||
schedule.hold()
|
log.info { "[ScheduleService.holdSchedule] Holding된 일정 없음: id=${id}, count = " }
|
||||||
return
|
throw ScheduleException(ScheduleErrorCode.SCHEDULE_NOT_AVAILABLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
throw ScheduleException(ScheduleErrorCode.SCHEDULE_NOT_AVAILABLE)
|
log.info { "[ScheduleService.holdSchedule] 일정 Holding 완료: id=$id" }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|||||||
@ -1,14 +1,9 @@
|
|||||||
package com.sangdol.roomescape.schedule.infrastructure.persistence
|
package com.sangdol.roomescape.schedule.infrastructure.persistence
|
||||||
|
|
||||||
import com.sangdol.common.persistence.PersistableBaseEntity
|
import com.sangdol.common.persistence.AuditingBaseEntity
|
||||||
import com.sangdol.common.utils.MdcPrincipalIdUtil
|
|
||||||
import jakarta.persistence.*
|
import jakarta.persistence.*
|
||||||
import org.springframework.data.annotation.CreatedBy
|
|
||||||
import org.springframework.data.annotation.CreatedDate
|
|
||||||
import org.springframework.data.annotation.LastModifiedDate
|
|
||||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@ -24,36 +19,13 @@ class ScheduleEntity(
|
|||||||
|
|
||||||
@Enumerated(value = EnumType.STRING)
|
@Enumerated(value = EnumType.STRING)
|
||||||
var status: ScheduleStatus,
|
var status: ScheduleStatus,
|
||||||
) : PersistableBaseEntity(id) {
|
) : AuditingBaseEntity(id) {
|
||||||
@Column(updatable = false)
|
|
||||||
@CreatedDate
|
|
||||||
lateinit var createdAt: LocalDateTime
|
|
||||||
|
|
||||||
@Column(updatable = false)
|
|
||||||
@CreatedBy
|
|
||||||
var createdBy: Long = 0L
|
|
||||||
|
|
||||||
@Column
|
|
||||||
@LastModifiedDate
|
|
||||||
lateinit var updatedAt: LocalDateTime
|
|
||||||
|
|
||||||
var updatedBy: Long = 0L
|
|
||||||
|
|
||||||
fun modifyIfNotNull(
|
fun modifyIfNotNull(
|
||||||
time: LocalTime?,
|
time: LocalTime?,
|
||||||
status: ScheduleStatus?
|
status: ScheduleStatus?
|
||||||
) {
|
) {
|
||||||
time?.let { this.time = it }
|
time?.let { this.time = it }
|
||||||
status?.let { this.status = it }
|
status?.let { this.status = it }
|
||||||
updateLastModifiedBy()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun hold() {
|
|
||||||
this.status = ScheduleStatus.HOLD
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateLastModifiedBy() {
|
|
||||||
MdcPrincipalIdUtil.extractAsLongOrNull()?.also { this.updatedBy = it }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +38,7 @@ object ScheduleEntityFactory {
|
|||||||
storeId = storeId,
|
storeId = storeId,
|
||||||
themeId = themeId,
|
themeId = themeId,
|
||||||
status = ScheduleStatus.AVAILABLE
|
status = ScheduleStatus.AVAILABLE
|
||||||
).apply { this.updateLastModifiedBy() }
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package com.sangdol.roomescape.schedule.infrastructure.persistence
|
|||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.data.jpa.repository.Query
|
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.Modifying
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
|
|
||||||
@ -82,4 +83,17 @@ interface ScheduleRepository : JpaRepository<ScheduleEntity, Long> {
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
fun findOverviewByIdOrNull(id: Long): ScheduleOverview?
|
fun findOverviewByIdOrNull(id: Long): ScheduleOverview?
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Query("""
|
||||||
|
UPDATE
|
||||||
|
ScheduleEntity s
|
||||||
|
SET
|
||||||
|
s.status = 'HOLD'
|
||||||
|
WHERE
|
||||||
|
s._id = :id
|
||||||
|
AND
|
||||||
|
s.status = 'AVAILABLE'
|
||||||
|
""")
|
||||||
|
fun holdAvailableScheduleById(id: Long): Int
|
||||||
}
|
}
|
||||||
|
|||||||
@ -130,7 +130,7 @@ class ScheduleApiTest(
|
|||||||
|
|
||||||
context("일정이 ${ScheduleStatus.AVAILABLE}이 아니면 실패한다.") {
|
context("일정이 ${ScheduleStatus.AVAILABLE}이 아니면 실패한다.") {
|
||||||
(ScheduleStatus.entries - ScheduleStatus.AVAILABLE).forEach {
|
(ScheduleStatus.entries - ScheduleStatus.AVAILABLE).forEach {
|
||||||
test("${it}") {
|
test("$it") {
|
||||||
val schedule = dummyInitializer.createSchedule(status = it)
|
val schedule = dummyInitializer.createSchedule(status = it)
|
||||||
|
|
||||||
runExceptionTest(
|
runExceptionTest(
|
||||||
@ -142,6 +142,15 @@ class ScheduleApiTest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("일정이 없으면 실패한다.") {
|
||||||
|
runExceptionTest(
|
||||||
|
token = testAuthUtil.defaultUserLogin().second,
|
||||||
|
method = HttpMethod.POST,
|
||||||
|
endpoint = "/schedules/${INVALID_PK}/hold",
|
||||||
|
expectedErrorCode = ScheduleErrorCode.SCHEDULE_NOT_AVAILABLE
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,94 @@
|
|||||||
|
package com.sangdol.roomescape.schedule
|
||||||
|
|
||||||
|
import com.sangdol.common.utils.MdcPrincipalIdUtil
|
||||||
|
import com.sangdol.roomescape.schedule.business.ScheduleService
|
||||||
|
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository
|
||||||
|
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
|
||||||
|
import com.sangdol.roomescape.schedule.web.ScheduleCreateRequest
|
||||||
|
import com.sangdol.roomescape.schedule.web.ScheduleUpdateRequest
|
||||||
|
import com.sangdol.roomescape.supports.FunSpecSpringbootTest
|
||||||
|
import com.sangdol.roomescape.supports.IDGenerator
|
||||||
|
import com.sangdol.roomescape.supports.initialize
|
||||||
|
import io.kotest.assertions.assertSoftly
|
||||||
|
import io.kotest.matchers.nulls.shouldNotBeNull
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import io.kotest.matchers.shouldNotBe
|
||||||
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalTime
|
||||||
|
|
||||||
|
class ScheduleServiceTest(
|
||||||
|
private val scheduleService: ScheduleService,
|
||||||
|
private val scheduleRepository: ScheduleRepository
|
||||||
|
) : FunSpecSpringbootTest() {
|
||||||
|
init {
|
||||||
|
afterTest {
|
||||||
|
MdcPrincipalIdUtil.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
test("관리자가 일정을 수정하면 updatedAt, updatedBy 컬럼이 변경된다. ") {
|
||||||
|
val createdScheduleId = createSchedule()
|
||||||
|
|
||||||
|
initialize("updatedBy 변경 확인을 위한 MDC 재설정") {
|
||||||
|
MdcPrincipalIdUtil.clear()
|
||||||
|
IDGenerator.create().also {
|
||||||
|
MdcPrincipalIdUtil.set(it.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleService.updateSchedule(
|
||||||
|
createdScheduleId,
|
||||||
|
ScheduleUpdateRequest(status = ScheduleStatus.RESERVED)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertSoftly(scheduleRepository.findByIdOrNull(createdScheduleId)) {
|
||||||
|
this.shouldNotBeNull()
|
||||||
|
this.updatedAt shouldNotBe this.createdAt
|
||||||
|
this.updatedBy shouldNotBe this.createdBy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("유저가 일정을 Holding 하는 경우에는 updatedAt, updatedBy 컬럼이 변경되지 않는다.") {
|
||||||
|
val createdScheduleId: Long = createSchedule()
|
||||||
|
|
||||||
|
initialize("updatedBy 변경 확인을 위한 MDC 재설정") {
|
||||||
|
MdcPrincipalIdUtil.clear()
|
||||||
|
IDGenerator.create().also {
|
||||||
|
MdcPrincipalIdUtil.set(it.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleService.holdSchedule(createdScheduleId)
|
||||||
|
|
||||||
|
assertSoftly(scheduleRepository.findByIdOrNull(createdScheduleId)) {
|
||||||
|
this.shouldNotBeNull()
|
||||||
|
this.updatedAt shouldBe this.createdAt
|
||||||
|
this.updatedBy shouldBe this.createdBy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createSchedule(): Long {
|
||||||
|
initialize("createdBy, updatedBy 설정을 위한 MDC 설정") {
|
||||||
|
IDGenerator.create().also {
|
||||||
|
MdcPrincipalIdUtil.set(it.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val (storeId, themeId) = initialize("FK 제약조건 해소를 위한 매장, 테마 생성") {
|
||||||
|
val store = dummyInitializer.createStore()
|
||||||
|
val theme = dummyInitializer.createTheme()
|
||||||
|
|
||||||
|
store.id to theme.id
|
||||||
|
}
|
||||||
|
|
||||||
|
return scheduleService.createSchedule(
|
||||||
|
storeId = storeId,
|
||||||
|
request = ScheduleCreateRequest(
|
||||||
|
date = LocalDate.now().plusDays(1),
|
||||||
|
time = LocalTime.now(),
|
||||||
|
themeId = themeId
|
||||||
|
)
|
||||||
|
).id
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user