refactor: schedule에서의 DTO 스펙 수정

This commit is contained in:
이상진 2025-10-07 19:32:08 +09:00
parent 7b0ebcc6dc
commit 07263426b2
10 changed files with 107 additions and 98 deletions

View File

@ -9,8 +9,9 @@ import com.sangdol.roomescape.reservation.exception.ReservationException
import com.sangdol.roomescape.reservation.infrastructure.persistence.*
import com.sangdol.roomescape.reservation.web.*
import com.sangdol.roomescape.schedule.business.ScheduleService
import com.sangdol.roomescape.schedule.dto.ScheduleStateResponse
import com.sangdol.roomescape.schedule.dto.ScheduleWithThemeAndStoreResponse
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
import com.sangdol.roomescape.schedule.web.ScheduleOverviewResponse
import com.sangdol.roomescape.theme.business.ThemeService
import com.sangdol.roomescape.user.business.UserService
import com.sangdol.roomescape.user.dto.UserContactResponse
@ -43,7 +44,7 @@ class ReservationService(
log.info { "[createPendingReservation] Pending 예약 생성 시작: schedule=${request.scheduleId}" }
run {
val schedule = scheduleService.findSummaryWithLock(request.scheduleId)
val schedule: ScheduleStateResponse = scheduleService.findStateWithLock(request.scheduleId)
val theme = themeService.findInfoById(schedule.themeId)
reservationValidator.validateCanCreate(schedule, theme, request)
@ -103,8 +104,16 @@ class ReservationService(
)
return ReservationOverviewListResponse(reservations.map {
val schedule: ScheduleOverviewResponse = scheduleService.findScheduleOverviewById(it.scheduleId)
it.toOverviewResponse(schedule)
val response: ScheduleWithThemeAndStoreResponse = scheduleService.findWithThemeAndStore(it.scheduleId)
val schedule = response.schedule
it.toOverviewResponse(
scheduleDate = schedule.date,
scheduleStartFrom = schedule.startFrom,
scheduleEndAt = schedule.endAt,
storeName = response.theme.name,
themeName = response.store.name
)
}).also {
log.info { "[findSummaryByMemberId] ${it.reservations.size}개의 예약 조회 완료: userId=${user.id}" }
}

View File

@ -5,8 +5,8 @@ import com.sangdol.common.utils.toKoreaDateTime
import com.sangdol.roomescape.reservation.exception.ReservationErrorCode
import com.sangdol.roomescape.reservation.exception.ReservationException
import com.sangdol.roomescape.reservation.web.PendingReservationCreateRequest
import com.sangdol.roomescape.schedule.dto.ScheduleStateResponse
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
import com.sangdol.roomescape.schedule.dto.ScheduleSummaryResponse
import com.sangdol.roomescape.theme.dto.ThemeInfoResponse
import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging
@ -20,14 +20,14 @@ private val log: KLogger = KotlinLogging.logger {}
class ReservationValidator {
fun validateCanCreate(
schedule: ScheduleSummaryResponse,
schedule: ScheduleStateResponse,
theme: ThemeInfoResponse,
request: PendingReservationCreateRequest
) {
validateSchedule(schedule)
validateReservationInfo(theme, request)
}
private fun validateSchedule(schedule: ScheduleSummaryResponse) {
private fun validateSchedule(schedule: ScheduleStateResponse) {
if (schedule.status != ScheduleStatus.HOLD) {
log.info { "[validateCanCreate] ${schedule.status}로의 일정 상태 변경에 따른 실패" }
throw ReservationException(ReservationErrorCode.EXPIRED_HELD_SCHEDULE)
@ -40,7 +40,7 @@ class ReservationValidator {
throw ReservationException(ReservationErrorCode.EXPIRED_HELD_SCHEDULE)
}
val scheduleDateTime = LocalDateTime.of(schedule.date, schedule.time)
val scheduleDateTime = LocalDateTime.of(schedule.date, schedule.startFrom)
val nowDateTime = KoreaDateTime.now()
if (scheduleDateTime.isBefore(nowDateTime)) {
log.info { "[validateCanCreate] 과거 시간인 일정으로 인한 실패: scheduleDateTime=${scheduleDateTime}(KST), now=${nowDateTime}" }

View File

@ -3,7 +3,6 @@ package com.sangdol.roomescape.reservation.web
import com.sangdol.roomescape.payment.web.PaymentWithDetailResponse
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationEntity
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
import com.sangdol.roomescape.schedule.web.ScheduleOverviewResponse
import com.sangdol.roomescape.user.dto.UserContactResponse
import jakarta.validation.constraints.NotEmpty
import java.time.Instant
@ -46,14 +45,18 @@ data class ReservationOverviewResponse(
)
fun ReservationEntity.toOverviewResponse(
schedule: ScheduleOverviewResponse
scheduleDate: LocalDate,
scheduleStartFrom: LocalTime,
scheduleEndAt: LocalTime,
storeName: String,
themeName: String
) = ReservationOverviewResponse(
id = this.id,
storeName = schedule.storeName,
themeName = schedule.themeName,
date = schedule.date,
startFrom = schedule.startFrom,
endAt = schedule.endAt,
storeName = storeName,
themeName = themeName,
date = scheduleDate,
startFrom = scheduleStartFrom,
endAt = scheduleEndAt,
status = this.status
)

View File

@ -15,7 +15,7 @@ import com.sangdol.roomescape.schedule.exception.ScheduleException
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntityFactory
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository
import com.sangdol.roomescape.schedule.mapper.toAdminSummaryListResponse
import com.sangdol.roomescape.schedule.mapper.toAdminSummaryResponse
import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.data.repository.findByIdOrNull
@ -32,9 +32,6 @@ class AdminScheduleService(
private val idGenerator: IDGenerator,
private val adminService: AdminService
) {
// ========================================
// All-Admin (본사, 매장 모두 사용가능)
// ========================================
@Transactional(readOnly = true)
fun searchSchedules(storeId: Long, date: LocalDate?, themeId: Long?): AdminScheduleSummaryListResponse {
log.info { "[searchSchedules] 일정 검색 시작: storeId=$storeId, date=$date, themeId=$themeId" }
@ -46,7 +43,7 @@ class AdminScheduleService(
.filter { (themeId == null) || (it.themeId == themeId) }
.sortedBy { it.time }
return schedules.toAdminSummaryListResponse()
return schedules.toAdminSummaryResponse()
.also {
log.info { "[searchSchedules] ${it.schedules.size} 개의 일정 조회 완료" }
}
@ -65,9 +62,6 @@ class AdminScheduleService(
.also { log.info { "[findDetail] 일정 감사 정보 조회 완료: id=$id" } }
}
// ========================================
// Store-Admin (매장 관리자 로그인 필요)
// ========================================
@Transactional
fun createSchedule(storeId: Long, request: ScheduleCreateRequest): ScheduleCreateResponse {
log.info { "[createSchedule] 일정 생성 시작: storeId=${storeId}, date=${request.date}, time=${request.time}, themeId=${request.themeId}" }

View File

@ -1,32 +1,21 @@
package com.sangdol.roomescape.schedule.business
import com.sangdol.common.persistence.IDGenerator
import com.sangdol.common.utils.KoreaDate
import com.sangdol.common.utils.KoreaTime
import com.sangdol.roomescape.admin.business.AdminService
import com.sangdol.roomescape.common.types.AuditingInfo
import com.sangdol.roomescape.common.types.Auditor
import com.sangdol.roomescape.schedule.business.domain.ScheduleOverview
import com.sangdol.roomescape.schedule.dto.AdminScheduleSummaryListResponse
import com.sangdol.roomescape.schedule.dto.ScheduleCreateRequest
import com.sangdol.roomescape.schedule.dto.ScheduleCreateResponse
import com.sangdol.roomescape.schedule.dto.ScheduleOverviewResponse
import com.sangdol.roomescape.schedule.dto.ScheduleSummaryResponse
import com.sangdol.roomescape.schedule.dto.ScheduleUpdateRequest
import com.sangdol.roomescape.schedule.dto.ScheduleStateResponse
import com.sangdol.roomescape.schedule.dto.ScheduleWithThemeAndStoreResponse
import com.sangdol.roomescape.schedule.dto.ScheduleWithThemeListResponse
import com.sangdol.roomescape.schedule.mapper.toAdminSummaryListResponse
import com.sangdol.roomescape.schedule.mapper.toOverviewResponse
import com.sangdol.roomescape.schedule.mapper.toResponse
import com.sangdol.roomescape.schedule.mapper.toSummaryResponse
import com.sangdol.roomescape.schedule.exception.ScheduleErrorCode
import com.sangdol.roomescape.schedule.exception.ScheduleException
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntityFactory
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
import com.sangdol.roomescape.schedule.mapper.toResponseWithTheme
import com.sangdol.roomescape.schedule.mapper.toResponseWithThemeAndStore
import com.sangdol.roomescape.schedule.mapper.toStateResponse
import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate
@ -55,7 +44,7 @@ class ScheduleService(
scheduleRepository.findStoreSchedulesWithThemeByDate(storeId, date)
.filter { it.date.isAfter(currentDate) || it.time.isAfter(currentTime) }
return schedules.toResponse()
return schedules.toResponseWithTheme()
.also {
log.info { "[getStoreScheduleByDate] storeId=${storeId}, date=$date${it.schedules.size}개 일정 조회 완료" }
}
@ -79,7 +68,7 @@ class ScheduleService(
}
@Transactional(readOnly = true)
fun findSummaryWithLock(id: Long): ScheduleSummaryResponse {
fun findStateWithLock(id: Long): ScheduleStateResponse {
log.info { "[findDateTimeById] 일정 개요 조회 시작 : id=$id" }
val schedule: ScheduleEntity = scheduleRepository.findByIdForUpdate(id)
@ -88,20 +77,20 @@ class ScheduleService(
throw ScheduleException(ScheduleErrorCode.SCHEDULE_NOT_FOUND)
}
return schedule.toSummaryResponse()
return schedule.toStateResponse()
.also {
log.info { "[findDateTimeById] 일정 개요 조회 완료: id=$id" }
}
}
@Transactional(readOnly = true)
fun findScheduleOverviewById(id: Long): ScheduleOverviewResponse {
fun findWithThemeAndStore(id: Long): ScheduleWithThemeAndStoreResponse {
val overview: ScheduleOverview = scheduleRepository.findOverviewByIdOrNull(id) ?: run {
log.warn { "[findScheduleOverview] 일정 개요 조회 실패: id=$id" }
throw ScheduleException(ScheduleErrorCode.SCHEDULE_NOT_FOUND)
}
return overview.toOverviewResponse()
return overview.toResponseWithThemeAndStore()
}
@Transactional

View File

@ -1,40 +1,47 @@
package com.sangdol.roomescape.schedule.dto
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
import com.sangdol.roomescape.theme.infrastructure.persistence.Difficulty
import java.time.Instant
import java.time.LocalDate
import java.time.LocalTime
data class ScheduleWithThemeResponse(
data class ScheduleResponse(
val id: Long,
val date: LocalDate,
val startFrom: LocalTime,
val endAt: LocalTime,
val themeId: Long,
val themeName: String,
val themeDifficulty: Difficulty,
val status: ScheduleStatus
val status: ScheduleStatus,
)
data class ScheduleWithThemeListResponse(
val schedules: List<ScheduleWithThemeResponse>
)
data class ScheduleSummaryResponse(
data class ScheduleStateResponse(
val date: LocalDate,
val time: LocalTime,
val startFrom: LocalTime,
val themeId: Long,
val status: ScheduleStatus,
val holdExpiredAt: Instant? = null
)
data class ScheduleOverviewResponse(
data class ScheduleThemeInfo(
val id: Long,
val storeId: Long,
val storeName: String,
val date: LocalDate,
val startFrom: LocalTime,
val endAt: LocalTime,
val themeId: Long,
val themeName: String,
val name: String
)
data class ScheduleStoreInfo(
val id: Long,
val name: String
)
data class ScheduleWithThemeResponse(
val schedule: ScheduleResponse,
val theme: ScheduleThemeInfo,
)
data class ScheduleWithThemeAndStoreResponse(
val schedule: ScheduleResponse,
val theme: ScheduleThemeInfo,
val store: ScheduleStoreInfo
)
data class ScheduleWithThemeListResponse(
val schedules: List<ScheduleWithThemeResponse>
)

View File

@ -1,6 +1,7 @@
package com.sangdol.roomescape.schedule.mapper
import com.sangdol.roomescape.schedule.business.domain.ScheduleOverview
import com.sangdol.roomescape.schedule.dto.AdminScheduleSummaryListResponse
import com.sangdol.roomescape.schedule.dto.AdminScheduleSummaryResponse
fun ScheduleOverview.toAdminSummaryResponse() = AdminScheduleSummaryResponse(
@ -9,4 +10,8 @@ fun ScheduleOverview.toAdminSummaryResponse() = AdminScheduleSummaryResponse(
startFrom = this.time,
endAt = this.getEndAt(),
status = this.status
)
)
fun List<ScheduleOverview>.toAdminSummaryResponse() = AdminScheduleSummaryListResponse(
this.map { it.toAdminSummaryResponse() }
)

View File

@ -4,39 +4,25 @@ import com.sangdol.roomescape.schedule.business.domain.ScheduleOverview
import com.sangdol.roomescape.schedule.dto.*
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity
fun ScheduleOverview.toOverviewResponse() = ScheduleOverviewResponse(
id = this.id,
storeId = this.storeId,
storeName = this.storeName,
fun ScheduleEntity.toStateResponse() = ScheduleStateResponse(
date = this.date,
startFrom = this.time,
endAt = this.getEndAt(),
themeId = this.themeId,
themeName = this.themeName,
)
fun ScheduleEntity.toSummaryResponse() = ScheduleSummaryResponse(
date = this.date,
time = this.time,
themeId = this.themeId,
status = this.status,
holdExpiredAt = this.holdExpiredAt
)
fun ScheduleOverview.toResponse() = ScheduleWithThemeResponse(
id = this.id,
startFrom = this.time,
endAt = this.getEndAt(),
themeId = this.themeId,
themeName = this.themeName,
themeDifficulty = this.themeDifficulty,
status = this.status
fun ScheduleOverview.toResponseWithThemeAndStore() = ScheduleWithThemeAndStoreResponse(
schedule = ScheduleResponse(this.id, this.date, this.time, this.getEndAt(), this.status),
theme = ScheduleThemeInfo(this.themeId, this.themeName),
store = ScheduleStoreInfo(this.storeId, this.storeName),
)
fun List<ScheduleOverview>.toAdminSummaryListResponse() = AdminScheduleSummaryListResponse(
this.map { it.toAdminSummaryResponse() }
fun ScheduleOverview.toResponseWithTheme() = ScheduleWithThemeResponse(
schedule = ScheduleResponse(this.id, this.date, this.time, this.getEndAt(), this.status),
theme = ScheduleThemeInfo(this.themeId, this.themeName),
)
fun List<ScheduleOverview>.toResponse() = ScheduleWithThemeListResponse(
this.map { it.toResponse() }
fun List<ScheduleOverview>.toResponseWithTheme() = ScheduleWithThemeListResponse(
this.map { it.toResponseWithTheme() }
)

View File

@ -58,16 +58,30 @@ class ScheduleApiTest(
body("data.schedules.size()", equalTo(size))
assertProperties(
props = setOf(
"id",
"startFrom",
"endAt",
"themeId",
"themeName",
"themeDifficulty",
"status"
"schedule",
"theme"
),
propsNameIfList = "schedules"
)
assertProperties(
props = setOf(
"id",
"date",
"startFrom",
"endAt",
"status"
),
propsNameIfList = "schedules.schedule"
)
assertProperties(
props = setOf(
"id",
"name",
),
propsNameIfList = "schedules.theme"
)
}
)
}

View File

@ -1,6 +1,7 @@
package com.sangdol.roomescape.schedule
import com.sangdol.common.utils.MdcPrincipalIdUtil
import com.sangdol.roomescape.schedule.business.AdminScheduleService
import com.sangdol.roomescape.schedule.business.ScheduleService
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
@ -19,6 +20,7 @@ import java.time.LocalDate
import java.time.LocalTime
class ScheduleServiceTest(
private val adminScheduleService: AdminScheduleService,
private val scheduleService: ScheduleService,
private val scheduleRepository: ScheduleRepository
) : FunSpecSpringbootTest() {
@ -37,7 +39,7 @@ class ScheduleServiceTest(
}
}
scheduleService.updateSchedule(
adminScheduleService.updateSchedule(
createdScheduleId,
ScheduleUpdateRequest(status = ScheduleStatus.RESERVED)
)
@ -107,7 +109,7 @@ class ScheduleServiceTest(
store.id to theme.id
}
return scheduleService.createSchedule(
return adminScheduleService.createSchedule(
storeId = storeId,
request = ScheduleCreateRequest(
date = LocalDate.now().plusDays(1),