refactor: Time 관련 네이밍 개선

- ReservationTime -> Time으로 접두사 수정
- DTO 네이밍 개선(-> 이후 다른 도메인에도 반영 예정)
This commit is contained in:
이상진 2025-07-22 14:26:08 +09:00
parent 7ec2621c67
commit 8befd45741
29 changed files with 321 additions and 335 deletions

View File

@ -26,7 +26,7 @@ enum class ErrorType(
// 404 Not Found // 404 Not Found
MEMBER_NOT_FOUND("회원(Member) 정보가 존재하지 않습니다."), MEMBER_NOT_FOUND("회원(Member) 정보가 존재하지 않습니다."),
RESERVATION_NOT_FOUND("예약(Reservation) 정보가 존재하지 않습니다."), RESERVATION_NOT_FOUND("예약(Reservation) 정보가 존재하지 않습니다."),
RESERVATION_TIME_NOT_FOUND("예약 시간(ReservationTime) 정보가 존재하지 않습니다."), TIME_NOT_FOUND("예약 시간(Time) 정보가 존재하지 않습니다."),
THEME_NOT_FOUND("테마(Theme) 정보가 존재하지 않습니다."), THEME_NOT_FOUND("테마(Theme) 정보가 존재하지 않습니다."),
PAYMENT_NOT_FOUND("결제(Payment) 정보가 존재하지 않습니다."), PAYMENT_NOT_FOUND("결제(Payment) 정보가 존재하지 않습니다."),

View File

@ -5,7 +5,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import roomescape.payment.infrastructure.client.PaymentCancelResponseDeserializer import roomescape.payment.infrastructure.client.PaymentCancelResponseDeserializer
import roomescape.payment.infrastructure.persistence.PaymentEntity import roomescape.payment.infrastructure.persistence.PaymentEntity
import roomescape.reservation.web.ReservationResponse import roomescape.reservation.web.ReservationResponse
import roomescape.reservation.web.toResponse import roomescape.reservation.web.toCreateResponse
import java.time.OffsetDateTime import java.time.OffsetDateTime
data class PaymentApproveRequest( data class PaymentApproveRequest(
@ -51,6 +51,6 @@ fun PaymentEntity.toReservationPaymentResponse(): ReservationPaymentResponse = R
orderId = this.orderId, orderId = this.orderId,
paymentKey = this.paymentKey, paymentKey = this.paymentKey,
totalAmount = this.totalAmount, totalAmount = this.totalAmount,
reservation = this.reservation.toResponse(), reservation = this.reservation.toCreateResponse(),
approvedAt = this.approvedAt approvedAt = this.approvedAt
) )

View File

@ -18,7 +18,7 @@ import java.time.LocalDateTime
@Transactional @Transactional
class ReservationService( class ReservationService(
private val reservationRepository: ReservationRepository, private val reservationRepository: ReservationRepository,
private val reservationTimeService: ReservationTimeService, private val timeService: TimeService,
private val memberService: MemberService, private val memberService: MemberService,
private val themeService: ThemeService, private val themeService: ThemeService,
) { ) {
@ -43,7 +43,7 @@ class ReservationService(
} }
private fun findAllReservationByStatus(spec: Specification<ReservationEntity>): List<ReservationResponse> { private fun findAllReservationByStatus(spec: Specification<ReservationEntity>): List<ReservationResponse> {
return reservationRepository.findAll(spec).map { it.toResponse() } return reservationRepository.findAll(spec).map { it.toCreateResponse() }
} }
fun removeReservationById(reservationId: Long, memberId: Long) { fun removeReservationById(reservationId: Long, memberId: Long) {
@ -96,7 +96,7 @@ class ReservationService(
): ReservationResponse = getReservationForSave(timeId, themeId, date, memberId, status) ): ReservationResponse = getReservationForSave(timeId, themeId, date, memberId, status)
.also { .also {
reservationRepository.save(it) reservationRepository.save(it)
}.toResponse() }.toCreateResponse()
private fun validateMemberAlreadyReserve(themeId: Long?, timeId: Long?, date: LocalDate?, memberId: Long?) { private fun validateMemberAlreadyReserve(themeId: Long?, timeId: Long?, date: LocalDate?, memberId: Long?) {
@ -127,10 +127,10 @@ class ReservationService(
private fun validateDateAndTime( private fun validateDateAndTime(
requestDate: LocalDate, requestDate: LocalDate,
requestReservationTime: ReservationTimeEntity requestTime: TimeEntity
) { ) {
val now = LocalDateTime.now() val now = LocalDateTime.now()
val request = LocalDateTime.of(requestDate, requestReservationTime.startAt) val request = LocalDateTime.of(requestDate, requestTime.startAt)
if (request.isBefore(now)) { if (request.isBefore(now)) {
throw RoomescapeException( throw RoomescapeException(
@ -148,7 +148,7 @@ class ReservationService(
memberId: Long, memberId: Long,
status: ReservationStatus status: ReservationStatus
): ReservationEntity { ): ReservationEntity {
val time = reservationTimeService.findTimeById(timeId) val time = timeService.findById(timeId)
val theme = themeService.findById(themeId) val theme = themeService.findById(themeId)
val member = memberService.findById(memberId) val member = memberService.findById(memberId)
@ -156,7 +156,7 @@ class ReservationService(
return ReservationEntity( return ReservationEntity(
date = date, date = date,
reservationTime = time, time = time,
theme = theme, theme = theme,
member = member, member = member,
reservationStatus = status reservationStatus = status

View File

@ -1,74 +0,0 @@
package roomescape.reservation.business
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import roomescape.common.exception.ErrorType
import roomescape.common.exception.RoomescapeException
import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.reservation.infrastructure.persistence.ReservationRepository
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository
import roomescape.reservation.web.*
import java.time.LocalDate
import java.time.LocalTime
@Service
class ReservationTimeService(
private val reservationTimeRepository: ReservationTimeRepository,
private val reservationRepository: ReservationRepository
) {
@Transactional(readOnly = true)
fun findTimeById(id: Long): ReservationTimeEntity = reservationTimeRepository.findByIdOrNull(id)
?: throw RoomescapeException(
ErrorType.RESERVATION_TIME_NOT_FOUND,
"[reservationTimeId: $id]",
HttpStatus.BAD_REQUEST
)
@Transactional(readOnly = true)
fun findAllTimes(): ReservationTimesResponse = reservationTimeRepository.findAll()
.toResponses()
@Transactional
fun addTime(reservationTimeRequest: ReservationTimeRequest): ReservationTimeResponse {
val startAt: LocalTime = reservationTimeRequest.startAt
if (reservationTimeRepository.existsByStartAt(startAt)) {
throw RoomescapeException(
ErrorType.TIME_DUPLICATED, "[startAt: $startAt]", HttpStatus.CONFLICT
)
}
return ReservationTimeEntity(startAt = startAt)
.also { reservationTimeRepository.save(it) }
.toResponse()
}
@Transactional
fun removeTimeById(id: Long) {
val reservationTime: ReservationTimeEntity = findTimeById(id)
reservationRepository.findByReservationTime(reservationTime)
.also {
if (it.isNotEmpty()) {
throw RoomescapeException(
ErrorType.TIME_IS_USED_CONFLICT, "[timeId: $id]", HttpStatus.CONFLICT
)
}
reservationTimeRepository.deleteById(id)
}
}
@Transactional(readOnly = true)
fun findAllAvailableTimesByDateAndTheme(date: LocalDate, themeId: Long): ReservationTimeInfosResponse {
val allTimes = reservationTimeRepository.findAll()
val reservations: List<ReservationEntity> = reservationRepository.findByDateAndThemeId(date, themeId)
return ReservationTimeInfosResponse(allTimes.map { time ->
val alreadyBooked: Boolean = reservations.any { reservation -> reservation.reservationTime.id == time.id }
time.toInfoResponse(alreadyBooked)
})
}
}

View File

@ -0,0 +1,74 @@
package roomescape.reservation.business
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import roomescape.common.exception.ErrorType
import roomescape.common.exception.RoomescapeException
import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.reservation.infrastructure.persistence.ReservationRepository
import roomescape.reservation.infrastructure.persistence.TimeEntity
import roomescape.reservation.infrastructure.persistence.TimeRepository
import roomescape.reservation.web.*
import java.time.LocalDate
import java.time.LocalTime
@Service
class TimeService(
private val timeRepository: TimeRepository,
private val reservationRepository: ReservationRepository
) {
@Transactional(readOnly = true)
fun findById(id: Long): TimeEntity = timeRepository.findByIdOrNull(id)
?: throw RoomescapeException(
ErrorType.TIME_NOT_FOUND,
"[timeId: $id]",
HttpStatus.BAD_REQUEST
)
@Transactional(readOnly = true)
fun findAll(): TimeRetrieveListResponse = timeRepository.findAll().toRetrieveListResponse()
@Transactional
fun create(timeCreateRequest: TimeCreateRequest): TimeCreateResponse {
val startAt: LocalTime = timeCreateRequest.startAt
if (timeRepository.existsByStartAt(startAt)) {
throw RoomescapeException(
ErrorType.TIME_DUPLICATED, "[startAt: $startAt]", HttpStatus.CONFLICT
)
}
return TimeEntity(startAt = startAt)
.also { timeRepository.save(it) }
.toCreateResponse()
}
@Transactional
fun deleteById(id: Long) {
val time: TimeEntity = findById(id)
reservationRepository.findByTime(time)
.also {
if (it.isNotEmpty()) {
throw RoomescapeException(
ErrorType.TIME_IS_USED_CONFLICT, "[timeId: $id]", HttpStatus.CONFLICT
)
}
timeRepository.deleteById(id)
}
}
@Transactional(readOnly = true)
fun findAllWithAvailability(date: LocalDate, themeId: Long): TimeWithAvailabilityListResponse {
val allTimes = timeRepository.findAll()
val reservations: List<ReservationEntity> = reservationRepository.findByDateAndThemeId(date, themeId)
return TimeWithAvailabilityListResponse(allTimes.map { time ->
val isAvailable: Boolean = reservations.none { reservation -> reservation.time.id == time.id }
TimeWithAvailabilityResponse(time.id!!, time.startAt, isAvailable)
})
}
}

View File

@ -12,39 +12,39 @@ import org.springframework.web.bind.annotation.RequestParam
import roomescape.auth.web.support.Admin import roomescape.auth.web.support.Admin
import roomescape.auth.web.support.LoginRequired import roomescape.auth.web.support.LoginRequired
import roomescape.common.dto.response.CommonApiResponse import roomescape.common.dto.response.CommonApiResponse
import roomescape.reservation.web.ReservationTimeInfosResponse import roomescape.reservation.web.TimeWithAvailabilityListResponse
import roomescape.reservation.web.ReservationTimeRequest import roomescape.reservation.web.TimeCreateRequest
import roomescape.reservation.web.ReservationTimeResponse import roomescape.reservation.web.TimeCreateResponse
import roomescape.reservation.web.ReservationTimesResponse import roomescape.reservation.web.TimeRetrieveListResponse
import java.time.LocalDate import java.time.LocalDate
@Tag(name = "4. 예약 시간 API", description = "예약 시간을 조회 / 추가 / 삭제할 때 사용합니다.") @Tag(name = "4. 예약 시간 API", description = "예약 시간을 조회 / 추가 / 삭제할 때 사용합니다.")
interface ReservationTimeAPI { interface TimeAPI {
@Admin @Admin
@Operation(summary = "모든 시간 조회", tags = ["관리자 로그인이 필요한 API"]) @Operation(summary = "모든 시간 조회", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
fun getAllTimes(): ResponseEntity<CommonApiResponse<ReservationTimesResponse>> fun findAll(): ResponseEntity<CommonApiResponse<TimeRetrieveListResponse>>
@Admin @Admin
@Operation(summary = "시간 추가", tags = ["관리자 로그인이 필요한 API"]) @Operation(summary = "시간 추가", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "201", description = "성공", useReturnTypeSchema = true)) @ApiResponses(ApiResponse(responseCode = "201", description = "성공", useReturnTypeSchema = true))
fun saveTime( fun create(
@Valid @RequestBody reservationTimeRequest: ReservationTimeRequest, @Valid @RequestBody timeCreateRequest: TimeCreateRequest,
): ResponseEntity<CommonApiResponse<ReservationTimeResponse>> ): ResponseEntity<CommonApiResponse<TimeCreateResponse>>
@Admin @Admin
@Operation(summary = "시간 삭제", tags = ["관리자 로그인이 필요한 API"]) @Operation(summary = "시간 삭제", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true)) @ApiResponses(ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true))
fun removeTime( fun deleteById(
@PathVariable id: Long @PathVariable id: Long
): ResponseEntity<CommonApiResponse<Unit>> ): ResponseEntity<CommonApiResponse<Unit>>
@LoginRequired @LoginRequired
@Operation(summary = "예약 가능 여부를 포함한 모든 시간 조회", tags = ["로그인이 필요한 API"]) @Operation(summary = "예약 가능 여부를 포함한 모든 시간 조회", tags = ["로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)) @ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
fun findAllAvailableReservationTimes( fun findAllWithAvailability(
@RequestParam date: LocalDate, @RequestParam date: LocalDate,
@RequestParam themeId: Long @RequestParam themeId: Long
): ResponseEntity<CommonApiResponse<ReservationTimeInfosResponse>> ): ResponseEntity<CommonApiResponse<TimeWithAvailabilityListResponse>>
} }

View File

@ -18,7 +18,7 @@ class ReservationEntity(
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "time_id", nullable = false) @JoinColumn(name = "time_id", nullable = false)
var reservationTime: ReservationTimeEntity, var time: TimeEntity,
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "theme_id", nullable = false) @JoinColumn(name = "theme_id", nullable = false)
@ -40,14 +40,8 @@ class ReservationEntity(
} }
} }
@Schema(description = "예약 상태를 나타냅니다.", allowableValues = ["CONFIRMED", "CONFIRMED_PAYMENT_REQUIRED", "WAITING"])
enum class ReservationStatus { enum class ReservationStatus {
@Schema(description = "결제가 완료된 예약")
CONFIRMED, CONFIRMED,
@Schema(description = "결제가 필요한 예약")
CONFIRMED_PAYMENT_REQUIRED, CONFIRMED_PAYMENT_REQUIRED,
@Schema(description = "대기 중인 예약")
WAITING WAITING
} }

View File

@ -10,7 +10,7 @@ import java.time.LocalDate
interface ReservationRepository interface ReservationRepository
: JpaRepository<ReservationEntity, Long>, JpaSpecificationExecutor<ReservationEntity> { : JpaRepository<ReservationEntity, Long>, JpaSpecificationExecutor<ReservationEntity> {
fun findByReservationTime(reservationTime: ReservationTimeEntity): List<ReservationEntity> fun findByTime(time: TimeEntity): List<ReservationEntity>
fun findByDateAndThemeId(date: LocalDate, themeId: Long): List<ReservationEntity> fun findByDateAndThemeId(date: LocalDate, themeId: Long): List<ReservationEntity>
@ -33,7 +33,7 @@ interface ReservationRepository
AND EXISTS ( AND EXISTS (
SELECT 1 FROM ReservationEntity r SELECT 1 FROM ReservationEntity r
WHERE r.theme.id = r2.theme.id WHERE r.theme.id = r2.theme.id
AND r.reservationTime.id = r2.reservationTime.id AND r.time.id = r2.time.id
AND r.date = r2.date AND r.date = r2.date
AND r.reservationStatus != 'WAITING' AND r.reservationStatus != 'WAITING'
) )
@ -46,9 +46,9 @@ interface ReservationRepository
r.id, r.id,
t.name, t.name,
r.date, r.date,
r.reservationTime.startAt, r.time.startAt,
r.reservationStatus, r.reservationStatus,
(SELECT COUNT (r2) * 1L FROM ReservationEntity r2 WHERE r2.theme = r.theme AND r2.date = r.date AND r2.reservationTime = r.reservationTime 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.paymentKey,
p.totalAmount p.totalAmount
) )

View File

@ -22,7 +22,7 @@ class ReservationSearchSpecification(
fun sameTimeId(timeId: Long?): ReservationSearchSpecification = andIfNotNull(timeId?.let { fun sameTimeId(timeId: Long?): ReservationSearchSpecification = andIfNotNull(timeId?.let {
Specification { root, _, cb -> Specification { root, _, cb ->
cb.equal(root.get<ReservationTimeEntity>("reservationTime").get<Long>("id"), timeId) cb.equal(root.get<TimeEntity>("time").get<Long>("id"), timeId)
} }
}) })

View File

@ -4,8 +4,8 @@ import jakarta.persistence.*
import java.time.LocalTime import java.time.LocalTime
@Entity @Entity
@Table(name = "reservation_time") @Table(name = "times")
class ReservationTimeEntity( class TimeEntity(
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null, var id: Long? = null,

View File

@ -3,6 +3,6 @@ package roomescape.reservation.infrastructure.persistence
import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.JpaRepository
import java.time.LocalTime import java.time.LocalTime
interface ReservationTimeRepository : JpaRepository<ReservationTimeEntity, Long> { interface TimeRepository : JpaRepository<TimeEntity, Long> {
fun existsByStartAt(startAt: LocalTime): Boolean fun existsByStartAt(startAt: LocalTime): Boolean
} }

View File

@ -46,7 +46,7 @@ data class MyReservationsResponse(
@Schema(name = "예약 정보", description = "예약 저장 및 조회 응답에 사용됩니다.") @Schema(name = "예약 정보", description = "예약 저장 및 조회 응답에 사용됩니다.")
data class ReservationResponse( data class ReservationResponse(
@field:Schema(description = "예약 번호. 예약을 식별할 때 사용합니다.") @field:Schema(description = "예약 번호. 예약을 식별할 때 사용합니다.")
val id: Long, val id: Long,
@ -59,7 +59,7 @@ data class ReservationResponse(
@field:Schema(description = "예약 시간 정보") @field:Schema(description = "예약 시간 정보")
@field:JsonProperty("time") @field:JsonProperty("time")
val time: ReservationTimeResponse, val time: TimeCreateResponse,
@field:Schema(description = "예약한 테마 정보") @field:Schema(description = "예약한 테마 정보")
@field:JsonProperty("theme") @field:JsonProperty("theme")
@ -69,11 +69,11 @@ data class ReservationResponse(
val status: ReservationStatus val status: ReservationStatus
) )
fun ReservationEntity.toResponse(): ReservationResponse = ReservationResponse( fun ReservationEntity.toCreateResponse(): ReservationResponse = ReservationResponse(
id = this.id!!, id = this.id!!,
date = this.date, date = this.date,
member = this.member.toResponse(), member = this.member.toResponse(),
time = this.reservationTime.toResponse(), time = this.time.toCreateResponse(),
theme = this.theme.toResponse(), theme = this.theme.toResponse(),
status = this.reservationStatus status = this.reservationStatus
) )

View File

@ -1,51 +0,0 @@
package roomescape.reservation.web
import jakarta.validation.Valid
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import roomescape.common.dto.response.CommonApiResponse
import roomescape.reservation.business.ReservationTimeService
import roomescape.reservation.docs.ReservationTimeAPI
import java.net.URI
import java.time.LocalDate
@RestController
class ReservationTimeController(
private val reservationTimeService: ReservationTimeService
) : ReservationTimeAPI {
@GetMapping("/times")
override fun getAllTimes(): ResponseEntity<CommonApiResponse<ReservationTimesResponse>> {
val response: ReservationTimesResponse = reservationTimeService.findAllTimes()
return ResponseEntity.ok(CommonApiResponse(response))
}
@PostMapping("/times")
override fun saveTime(
@Valid @RequestBody reservationTimeRequest: ReservationTimeRequest,
): ResponseEntity<CommonApiResponse<ReservationTimeResponse>> {
val response: ReservationTimeResponse = reservationTimeService.addTime(reservationTimeRequest)
return ResponseEntity
.created(URI.create("/times/${response.id}"))
.body(CommonApiResponse(response))
}
@DeleteMapping("/times/{id}")
override fun removeTime(@PathVariable id: Long): ResponseEntity<CommonApiResponse<Unit>> {
reservationTimeService.removeTimeById(id)
return ResponseEntity.noContent().build()
}
@GetMapping("/times/filter")
override fun findAllAvailableReservationTimes(
@RequestParam date: LocalDate,
@RequestParam themeId: Long
): ResponseEntity<CommonApiResponse<ReservationTimeInfosResponse>> {
val response: ReservationTimeInfosResponse = reservationTimeService.findAllAvailableTimesByDateAndTheme(date, themeId)
return ResponseEntity.ok(CommonApiResponse(response))
}
}

View File

@ -1,58 +0,0 @@
package roomescape.reservation.web
import io.swagger.v3.oas.annotations.media.Schema
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity
import java.time.LocalTime
@Schema(name = "예약 시간 저장 요청", description = "예약 시간 저장 요청시 사용됩니다.")
data class ReservationTimeRequest(
@field:Schema(description = "예약 시간. HH:mm 형식으로 입력해야 합니다.", type = "string", example = "09:00")
val startAt: LocalTime
)
@Schema(name = "예약 시간 정보", description = "예약 시간 추가 및 조회 응답시 사용됩니다.")
data class ReservationTimeResponse(
@field:Schema(description = "예약 시간 번호. 예약 시간을 식별할 때 사용합니다.")
val id: Long,
@field:Schema(description = "예약 시간", type = "string", example = "09:00")
val startAt: LocalTime
)
fun ReservationTimeEntity.toResponse(): ReservationTimeResponse = ReservationTimeResponse(
this.id!!, this.startAt
)
@Schema(name = "예약 시간 정보 목록 응답", description = "모든 예약 시간 조회 응답시 사용됩니다.")
data class ReservationTimesResponse(
@field:Schema(description = "모든 시간 목록")
val times: List<ReservationTimeResponse>
)
fun List<ReservationTimeEntity>.toResponses(): ReservationTimesResponse = ReservationTimesResponse(
this.map { it.toResponse() }
)
@Schema(name = "특정 테마, 날짜에 대한 시간 정보 응답", description = "특정 날짜와 테마에 대해, 예약 가능 여부를 포함한 시간 정보를 저장합니다.")
data class ReservationTimeInfoResponse(
@field:Schema(description = "예약 시간 번호. 예약 시간을 식별할 때 사용합니다.")
val id: Long,
@field:Schema(description = "예약 시간", type = "string", example = "09:00")
val startAt: LocalTime,
@field:Schema(description = "이미 예약이 완료된 시간인지 여부")
val alreadyBooked: Boolean
)
fun ReservationTimeEntity.toInfoResponse(alreadyBooked: Boolean): ReservationTimeInfoResponse = ReservationTimeInfoResponse(
id = this.id!!,
startAt = this.startAt,
alreadyBooked = alreadyBooked
)
@Schema(name = "예약 시간 정보 목록 응답", description = "특정 테마, 날짜에 대한 모든 예약 가능 시간 정보를 저장합니다.")
data class ReservationTimeInfosResponse(
@field:Schema(description = "특정 테마, 날짜에 대한 예약 가능 여부를 포함한 시간 목록")
val times: List<ReservationTimeInfoResponse>
)

View File

@ -0,0 +1,51 @@
package roomescape.reservation.web
import jakarta.validation.Valid
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import roomescape.common.dto.response.CommonApiResponse
import roomescape.reservation.business.TimeService
import roomescape.reservation.docs.TimeAPI
import java.net.URI
import java.time.LocalDate
@RestController
class TimeController(
private val timeService: TimeService
) : TimeAPI {
@GetMapping("/times")
override fun findAll(): ResponseEntity<CommonApiResponse<TimeRetrieveListResponse>> {
val response: TimeRetrieveListResponse = timeService.findAll()
return ResponseEntity.ok(CommonApiResponse(response))
}
@PostMapping("/times")
override fun create(
@Valid @RequestBody timeCreateRequest: TimeCreateRequest,
): ResponseEntity<CommonApiResponse<TimeCreateResponse>> {
val response: TimeCreateResponse = timeService.create(timeCreateRequest)
return ResponseEntity
.created(URI.create("/times/${response.id}"))
.body(CommonApiResponse(response))
}
@DeleteMapping("/times/{id}")
override fun deleteById(@PathVariable id: Long): ResponseEntity<CommonApiResponse<Unit>> {
timeService.deleteById(id)
return ResponseEntity.noContent().build()
}
@GetMapping("/times/filter")
override fun findAllWithAvailability(
@RequestParam date: LocalDate,
@RequestParam themeId: Long
): ResponseEntity<CommonApiResponse<TimeWithAvailabilityListResponse>> {
val response: TimeWithAvailabilityListResponse = timeService.findAllWithAvailability(date, themeId)
return ResponseEntity.ok(CommonApiResponse(response))
}
}

View File

@ -0,0 +1,55 @@
package roomescape.reservation.web
import io.swagger.v3.oas.annotations.media.Schema
import roomescape.reservation.infrastructure.persistence.TimeEntity
import java.time.LocalTime
@Schema(name = "예약 시간 저장 요청", description = "예약 시간 저장 요청시 사용됩니다.")
data class TimeCreateRequest(
@field:Schema(description = "시간", type = "string", example = "09:00")
val startAt: LocalTime
)
@Schema(name = "예약 시간 정보", description = "예약 시간 추가 및 조회 응답시 사용됩니다.")
data class TimeCreateResponse(
@field:Schema(description = "시간 식별자")
val id: Long,
@field:Schema(description = "시간")
val startAt: LocalTime
)
fun TimeEntity.toCreateResponse(): TimeCreateResponse = TimeCreateResponse(this.id!!, this.startAt)
data class TimeRetrieveResponse(
@field:Schema(description = "시간 식별자.")
val id: Long,
@field:Schema(description = "시간")
val startAt: LocalTime
)
fun TimeEntity.toRetrieveResponse(): TimeRetrieveResponse = TimeRetrieveResponse(this.id!!, this.startAt)
data class TimeRetrieveListResponse(
val times: List<TimeRetrieveResponse>
)
fun List<TimeEntity>.toRetrieveListResponse(): TimeRetrieveListResponse = TimeRetrieveListResponse(
this.map { it.toRetrieveResponse() }
)
data class TimeWithAvailabilityResponse(
@field:Schema(description = "시간 식별자")
val id: Long,
@field:Schema(description = "시간")
val startAt: LocalTime,
@field:Schema(description = "예약 가능 여부")
val isAvailable: Boolean
)
data class TimeWithAvailabilityListResponse(
val times: List<TimeWithAvailabilityResponse>
)

View File

@ -1,10 +1,10 @@
insert into reservation_time(start_at) insert into times(start_at)
values ('15:00'); values ('15:00');
insert into reservation_time(start_at) insert into times(start_at)
values ('16:00'); values ('16:00');
insert into reservation_time(start_at) insert into times(start_at)
values ('17:00'); values ('17:00');
insert into reservation_time(start_at) insert into times(start_at)
values ('18:00'); values ('18:00');
insert into theme(name, description, thumbnail) insert into theme(name, description, thumbnail)

View File

@ -124,9 +124,9 @@ function renderAvailableTimes(times) {
times.data.times.forEach(time => { times.data.times.forEach(time => {
const startAt = time.startAt; const startAt = time.startAt;
const timeId = time.id; const timeId = time.id;
const alreadyBooked = time.alreadyBooked; const isAvailable = time.isAvailable;
const div = createSlot('time', startAt, timeId, alreadyBooked); // createSlot('time', 시작 시간, time id, 예약 여부) const div = createSlot('time', startAt, timeId, isAvailable); // createSlot('time', 시작 시간, time id, 예약 여부)
timeSlots.appendChild(div); timeSlots.appendChild(div);
}); });
} }
@ -139,7 +139,7 @@ function checkDateAndThemeAndTime() {
const waitButton = document.getElementById("wait-button"); const waitButton = document.getElementById("wait-button");
if (selectedDate && selectedThemeElement && selectedTimeElement) { if (selectedDate && selectedThemeElement && selectedTimeElement) {
if (selectedTimeElement.getAttribute('data-time-booked') === 'true') { if (selectedTimeElement.getAttribute('data-time-booked') === 'false') {
// 선택된 시간이 이미 예약된 경우 // 선택된 시간이 이미 예약된 경우
reserveButton.classList.add("disabled"); reserveButton.classList.add("disabled");
waitButton.classList.remove("disabled"); // 예약 대기 버튼 활성화 waitButton.classList.remove("disabled"); // 예약 대기 버튼 활성화

View File

@ -94,7 +94,7 @@ class PaymentRepositoryTest(
return ReservationFixture.create().also { return ReservationFixture.create().also {
entityManager.persist(it.member) entityManager.persist(it.member)
entityManager.persist(it.theme) entityManager.persist(it.theme)
entityManager.persist(it.reservationTime) entityManager.persist(it.time)
entityManager.persist(it) entityManager.persist(it)
entityManager.flush() entityManager.flush()

View File

@ -13,19 +13,19 @@ import roomescape.reservation.infrastructure.persistence.ReservationRepository
import roomescape.theme.business.ThemeService import roomescape.theme.business.ThemeService
import roomescape.util.MemberFixture import roomescape.util.MemberFixture
import roomescape.util.ReservationFixture import roomescape.util.ReservationFixture
import roomescape.util.ReservationTimeFixture import roomescape.util.TimeFixture
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
class ReservationServiteTest : FunSpec({ class ReservationServiteTest : FunSpec({
val reservationRepository: ReservationRepository = mockk() val reservationRepository: ReservationRepository = mockk()
val reservationTimeService: ReservationTimeService = mockk() val timeService: TimeService = mockk()
val memberService: MemberService = mockk() val memberService: MemberService = mockk()
val themeService: ThemeService = mockk() val themeService: ThemeService = mockk()
val reservationService = ReservationService( val reservationService = ReservationService(
reservationRepository, reservationRepository,
reservationTimeService, timeService,
memberService, memberService,
themeService themeService
) )
@ -65,8 +65,8 @@ class ReservationServiteTest : FunSpec({
) )
every { every {
reservationTimeService.findTimeById(any()) timeService.findById(any())
} returns ReservationTimeFixture.create() } returns TimeFixture.create()
shouldThrow<RoomescapeException> { shouldThrow<RoomescapeException> {
reservationService.addReservation(reservationRequest, 1L) reservationService.addReservation(reservationRequest, 1L)
@ -81,8 +81,8 @@ class ReservationServiteTest : FunSpec({
) )
every { every {
reservationTimeService.findTimeById(reservationRequest.timeId) timeService.findById(reservationRequest.timeId)
} returns ReservationTimeFixture.create( } returns TimeFixture.create(
startAt = LocalTime.now().minusMinutes(1) startAt = LocalTime.now().minusMinutes(1)
) )

View File

@ -32,7 +32,7 @@ class ReservationWithPaymentServiceTest : FunSpec({
val reservationEntity: ReservationEntity = ReservationFixture.create( val reservationEntity: ReservationEntity = ReservationFixture.create(
id = 1L, id = 1L,
date = reservationRequest.date, date = reservationRequest.date,
reservationTime = ReservationTimeFixture.create(id = reservationRequest.timeId), time = TimeFixture.create(id = reservationRequest.timeId),
theme = ThemeFixture.create(id = reservationRequest.themeId), theme = ThemeFixture.create(id = reservationRequest.themeId),
member = MemberFixture.create(id = memberId), member = MemberFixture.create(id = memberId),
status = ReservationStatus.CONFIRMED status = ReservationStatus.CONFIRMED
@ -65,7 +65,7 @@ class ReservationWithPaymentServiceTest : FunSpec({
this.id shouldBe reservationEntity.id this.id shouldBe reservationEntity.id
this.date shouldBe reservationEntity.date this.date shouldBe reservationEntity.date
this.member.id shouldBe reservationEntity.member.id this.member.id shouldBe reservationEntity.member.id
this.time.id shouldBe reservationEntity.reservationTime.id this.time.id shouldBe reservationEntity.time.id
this.theme.id shouldBe reservationEntity.theme.id this.theme.id shouldBe reservationEntity.theme.id
this.status shouldBe ReservationStatus.CONFIRMED this.status shouldBe ReservationStatus.CONFIRMED
} }

View File

@ -10,17 +10,17 @@ import org.springframework.http.HttpStatus
import roomescape.common.exception.ErrorType import roomescape.common.exception.ErrorType
import roomescape.common.exception.RoomescapeException import roomescape.common.exception.RoomescapeException
import roomescape.reservation.infrastructure.persistence.ReservationRepository import roomescape.reservation.infrastructure.persistence.ReservationRepository
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository import roomescape.reservation.infrastructure.persistence.TimeRepository
import roomescape.reservation.web.ReservationTimeRequest import roomescape.reservation.web.TimeCreateRequest
import roomescape.util.ReservationTimeFixture import roomescape.util.TimeFixture
import java.time.LocalTime import java.time.LocalTime
class ReservationTimeServiceTest : FunSpec({ class TimeServiceTest : FunSpec({
val reservationTimeRepository: ReservationTimeRepository = mockk() val timeRepository: TimeRepository = mockk()
val reservationRepository: ReservationRepository = mockk() val reservationRepository: ReservationRepository = mockk()
val reservationTimeService = ReservationTimeService( val timeService = TimeService(
reservationTimeRepository = reservationTimeRepository, timeRepository = timeRepository,
reservationRepository = reservationRepository reservationRepository = reservationRepository
) )
@ -28,13 +28,12 @@ class ReservationTimeServiceTest : FunSpec({
test("시간을 찾을 수 없으면 400 에러를 던진다.") { test("시간을 찾을 수 없으면 400 에러를 던진다.") {
val id = 1L val id = 1L
// Mocking the behavior of reservationTimeRepository.findByIdOrNull every { timeRepository.findByIdOrNull(id) } returns null
every { reservationTimeRepository.findByIdOrNull(id) } returns null
shouldThrow<RoomescapeException> { shouldThrow<RoomescapeException> {
reservationTimeService.findTimeById(id) timeService.findById(id)
}.apply { }.apply {
errorType shouldBe ErrorType.RESERVATION_TIME_NOT_FOUND errorType shouldBe ErrorType.TIME_NOT_FOUND
httpStatus shouldBe HttpStatus.BAD_REQUEST httpStatus shouldBe HttpStatus.BAD_REQUEST
} }
} }
@ -42,13 +41,12 @@ class ReservationTimeServiceTest : FunSpec({
context("addTime") { context("addTime") {
test("중복된 시간이 있으면 409 에러를 던진다.") { test("중복된 시간이 있으면 409 에러를 던진다.") {
val request = ReservationTimeRequest(startAt = LocalTime.of(10, 0)) val request = TimeCreateRequest(startAt = LocalTime.of(10, 0))
// Mocking the behavior of reservationTimeRepository.findByStartAt every { timeRepository.existsByStartAt(request.startAt) } returns true
every { reservationTimeRepository.existsByStartAt(request.startAt) } returns true
shouldThrow<RoomescapeException> { shouldThrow<RoomescapeException> {
reservationTimeService.addTime(request) timeService.create(request)
}.apply { }.apply {
errorType shouldBe ErrorType.TIME_DUPLICATED errorType shouldBe ErrorType.TIME_DUPLICATED
httpStatus shouldBe HttpStatus.CONFLICT httpStatus shouldBe HttpStatus.CONFLICT
@ -60,29 +58,26 @@ class ReservationTimeServiceTest : FunSpec({
test("시간을 찾을 수 없으면 400 에러를 던진다.") { test("시간을 찾을 수 없으면 400 에러를 던진다.") {
val id = 1L val id = 1L
// Mocking the behavior of reservationTimeRepository.findByIdOrNull every { timeRepository.findByIdOrNull(id) } returns null
every { reservationTimeRepository.findByIdOrNull(id) } returns null
shouldThrow<RoomescapeException> { shouldThrow<RoomescapeException> {
reservationTimeService.removeTimeById(id) timeService.deleteById(id)
}.apply { }.apply {
errorType shouldBe ErrorType.RESERVATION_TIME_NOT_FOUND errorType shouldBe ErrorType.TIME_NOT_FOUND
httpStatus shouldBe HttpStatus.BAD_REQUEST httpStatus shouldBe HttpStatus.BAD_REQUEST
} }
} }
test("예약이 있는 시간이면 409 에러를 던진다.") { test("예약이 있는 시간이면 409 에러를 던진다.") {
val id = 1L val id = 1L
val reservationTime = ReservationTimeFixture.create() val time = TimeFixture.create()
// Mocking the behavior of reservationTimeRepository.findByIdOrNull every { timeRepository.findByIdOrNull(id) } returns time
every { reservationTimeRepository.findByIdOrNull(id) } returns reservationTime
every { reservationRepository.findByTime(time) } returns listOf(mockk())
// Mocking the behavior of reservationRepository.findByReservationTime
every { reservationRepository.findByReservationTime(reservationTime) } returns listOf(mockk())
shouldThrow<RoomescapeException> { shouldThrow<RoomescapeException> {
reservationTimeService.removeTimeById(id) timeService.deleteById(id)
}.apply { }.apply {
errorType shouldBe ErrorType.TIME_IS_USED_CONFLICT errorType shouldBe ErrorType.TIME_IS_USED_CONFLICT
httpStatus shouldBe HttpStatus.CONFLICT httpStatus shouldBe HttpStatus.CONFLICT

View File

@ -12,7 +12,7 @@ import roomescape.reservation.web.MyReservationResponse
import roomescape.theme.infrastructure.persistence.ThemeEntity import roomescape.theme.infrastructure.persistence.ThemeEntity
import roomescape.util.PaymentFixture import roomescape.util.PaymentFixture
import roomescape.util.ReservationFixture import roomescape.util.ReservationFixture
import roomescape.util.ReservationTimeFixture import roomescape.util.TimeFixture
import roomescape.util.ThemeFixture import roomescape.util.ThemeFixture
@DataJpaTest @DataJpaTest
@ -21,13 +21,13 @@ class ReservationRepositoryTest(
val reservationRepository: ReservationRepository, val reservationRepository: ReservationRepository,
) : FunSpec() { ) : FunSpec() {
init { init {
context("findByReservationTime") { context("findByTime") {
val time = ReservationTimeFixture.create() val time = TimeFixture.create()
beforeTest { beforeTest {
listOf( listOf(
ReservationFixture.create(reservationTime = time), ReservationFixture.create(time = time),
ReservationFixture.create(reservationTime = ReservationTimeFixture.create( ReservationFixture.create(time = TimeFixture.create(
startAt = time.startAt.plusSeconds(1) startAt = time.startAt.plusSeconds(1)
)) ))
).forEach { ).forEach {
@ -39,9 +39,9 @@ class ReservationRepositoryTest(
} }
test("입력된 시간과 일치하는 예약을 반환한다.") { test("입력된 시간과 일치하는 예약을 반환한다.") {
assertSoftly(reservationRepository.findByReservationTime(time)) { assertSoftly(reservationRepository.findByTime(time)) {
it shouldHaveSize 1 it shouldHaveSize 1
assertSoftly(it.first().reservationTime.startAt) { result -> assertSoftly(it.first().time.startAt) { result ->
result.hour shouldBe time.startAt.hour result.hour shouldBe time.startAt.hour
result.minute shouldBe time.startAt.minute result.minute shouldBe time.startAt.minute
} }
@ -68,7 +68,7 @@ class ReservationRepositoryTest(
ReservationFixture.create(date = date.plusDays(1), theme = theme1), ReservationFixture.create(date = date.plusDays(1), theme = theme1),
ReservationFixture.create(date = date, theme = theme2), ReservationFixture.create(date = date, theme = theme2),
).forEach { ).forEach {
entityManager.persist(it.reservationTime) entityManager.persist(it.time)
entityManager.persist(it.member) entityManager.persist(it.member)
entityManager.persist(it) entityManager.persist(it)
} }
@ -192,7 +192,7 @@ class ReservationRepositoryTest(
} }
fun persistReservation(reservation: ReservationEntity) { fun persistReservation(reservation: ReservationEntity) {
entityManager.persist(reservation.reservationTime) entityManager.persist(reservation.time)
entityManager.persist(reservation.theme) entityManager.persist(reservation.theme)
entityManager.persist(reservation.member) entityManager.persist(reservation.member)
entityManager.persist(reservation) entityManager.persist(reservation)

View File

@ -10,7 +10,7 @@ import roomescape.member.infrastructure.persistence.MemberEntity
import roomescape.theme.infrastructure.persistence.ThemeEntity import roomescape.theme.infrastructure.persistence.ThemeEntity
import roomescape.util.MemberFixture import roomescape.util.MemberFixture
import roomescape.util.ReservationFixture import roomescape.util.ReservationFixture
import roomescape.util.ReservationTimeFixture import roomescape.util.TimeFixture
import roomescape.util.ThemeFixture import roomescape.util.ThemeFixture
import java.time.LocalDate import java.time.LocalDate
@ -25,7 +25,7 @@ class ReservationSearchSpecificationTest(
lateinit var confirmedNotPaidYesterday: ReservationEntity lateinit var confirmedNotPaidYesterday: ReservationEntity
lateinit var waitingTomorrow: ReservationEntity lateinit var waitingTomorrow: ReservationEntity
lateinit var member: MemberEntity lateinit var member: MemberEntity
lateinit var reservationTime: ReservationTimeEntity lateinit var time: TimeEntity
lateinit var theme: ThemeEntity lateinit var theme: ThemeEntity
"동일한 테마의 예약을 조회한다" { "동일한 테마의 예약을 조회한다" {
@ -56,7 +56,7 @@ class ReservationSearchSpecificationTest(
"동일한 예약 시간의 예약을 조회한다" { "동일한 예약 시간의 예약을 조회한다" {
val spec = ReservationSearchSpecification() val spec = ReservationSearchSpecification()
.sameTimeId(reservationTime.id) .sameTimeId(time.id)
.build() .build()
val results: List<ReservationEntity> = reservationRepository.findAll(spec) val results: List<ReservationEntity> = reservationRepository.findAll(spec)
@ -136,7 +136,7 @@ class ReservationSearchSpecificationTest(
member = MemberFixture.create().also { member = MemberFixture.create().also {
entityManager.persist(it) entityManager.persist(it)
} }
reservationTime = ReservationTimeFixture.create().also { time = TimeFixture.create().also {
entityManager.persist(it) entityManager.persist(it)
} }
theme = ThemeFixture.create().also { theme = ThemeFixture.create().also {
@ -144,7 +144,7 @@ class ReservationSearchSpecificationTest(
} }
confirmedNow = ReservationFixture.create( confirmedNow = ReservationFixture.create(
reservationTime = reservationTime, time = time,
member = member, member = member,
theme = theme, theme = theme,
date = LocalDate.now(), date = LocalDate.now(),
@ -154,7 +154,7 @@ class ReservationSearchSpecificationTest(
} }
confirmedNotPaidYesterday = ReservationFixture.create( confirmedNotPaidYesterday = ReservationFixture.create(
reservationTime = reservationTime, time = time,
member = member, member = member,
theme = theme, theme = theme,
date = LocalDate.now().minusDays(1), date = LocalDate.now().minusDays(1),
@ -164,7 +164,7 @@ class ReservationSearchSpecificationTest(
} }
waitingTomorrow = ReservationFixture.create( waitingTomorrow = ReservationFixture.create(
reservationTime = reservationTime, time = time,
member = member, member = member,
theme = theme, theme = theme,
date = LocalDate.now().plusDays(1), date = LocalDate.now().plusDays(1),

View File

@ -4,30 +4,30 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import jakarta.persistence.EntityManager import jakarta.persistence.EntityManager
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import roomescape.util.ReservationTimeFixture import roomescape.util.TimeFixture
import java.time.LocalTime import java.time.LocalTime
@DataJpaTest @DataJpaTest
class ReservationTimeRepositoryTest( class TimeRepositoryTest(
val entityManager: EntityManager, val entityManager: EntityManager,
val reservationTimeRepository: ReservationTimeRepository, val timeRepository: TimeRepository,
) : FunSpec({ ) : FunSpec({
context("existsByStartAt") { context("existsByStartAt") {
val startAt = LocalTime.of(10, 0) val startAt = LocalTime.of(10, 0)
beforeTest { beforeTest {
entityManager.persist(ReservationTimeFixture.create(startAt = startAt)) entityManager.persist(TimeFixture.create(startAt = startAt))
entityManager.flush() entityManager.flush()
entityManager.clear() entityManager.clear()
} }
test("동일한 시간이 있으면 true 반환") { test("동일한 시간이 있으면 true 반환") {
reservationTimeRepository.existsByStartAt(startAt) shouldBe true timeRepository.existsByStartAt(startAt) shouldBe true
} }
test("동일한 시간이 없으면 false 반환") { test("동일한 시간이 없으면 false 반환") {
reservationTimeRepository.existsByStartAt(startAt.plusSeconds(1)) shouldBe false timeRepository.existsByStartAt(startAt.plusSeconds(1)) shouldBe false
} }
} }
}) })

View File

@ -28,7 +28,7 @@ import roomescape.payment.infrastructure.client.TossPaymentClient
import roomescape.payment.infrastructure.persistence.PaymentEntity import roomescape.payment.infrastructure.persistence.PaymentEntity
import roomescape.reservation.infrastructure.persistence.ReservationEntity import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.reservation.infrastructure.persistence.ReservationStatus import roomescape.reservation.infrastructure.persistence.ReservationStatus
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity import roomescape.reservation.infrastructure.persistence.TimeEntity
import roomescape.theme.infrastructure.persistence.ThemeEntity import roomescape.theme.infrastructure.persistence.ThemeEntity
import roomescape.util.* import roomescape.util.*
import java.time.LocalDate import java.time.LocalDate
@ -503,7 +503,7 @@ class ReservationControllerTest(
val reservation = ReservationFixture.create( val reservation = ReservationFixture.create(
date = reservationRequest.date, date = reservationRequest.date,
theme = entityManager.find(ThemeEntity::class.java, reservationRequest.themeId), theme = entityManager.find(ThemeEntity::class.java, reservationRequest.themeId),
reservationTime = entityManager.find(ReservationTimeEntity::class.java, reservationRequest.timeId), time = entityManager.find(TimeEntity::class.java, reservationRequest.timeId),
member = member, member = member,
status = ReservationStatus.WAITING status = ReservationStatus.WAITING
) )
@ -675,7 +675,7 @@ class ReservationControllerTest(
return ReservationFixture.create( return ReservationFixture.create(
date = date, date = date,
theme = ThemeFixture.create(name = themeName), theme = ThemeFixture.create(name = themeName),
reservationTime = ReservationTimeFixture.create(startAt = time), time = TimeFixture.create(startAt = time),
member = member, member = member,
status = status status = status
).also { it -> ).also { it ->
@ -683,7 +683,7 @@ class ReservationControllerTest(
if (member.id == null) { if (member.id == null) {
entityManager.persist(member) entityManager.persist(member)
} }
entityManager.persist(it.reservationTime) entityManager.persist(it.time)
entityManager.persist(it.theme) entityManager.persist(it.theme)
entityManager.persist(it) entityManager.persist(it)
entityManager.flush() entityManager.flush()
@ -710,14 +710,14 @@ class ReservationControllerTest(
transactionTemplate.executeWithoutResult { transactionTemplate.executeWithoutResult {
repeat(10) { index -> repeat(10) { index ->
val theme = ThemeFixture.create(name = "theme$index") val theme = ThemeFixture.create(name = "theme$index")
val time = ReservationTimeFixture.create(startAt = LocalTime.now().plusMinutes(index.toLong())) val time = TimeFixture.create(startAt = LocalTime.now().plusMinutes(index.toLong()))
entityManager.persist(theme) entityManager.persist(theme)
entityManager.persist(time) entityManager.persist(time)
val reservation = ReservationFixture.create( val reservation = ReservationFixture.create(
date = LocalDate.now().plusDays(index.toLong()), date = LocalDate.now().plusDays(index.toLong()),
theme = theme, theme = theme,
reservationTime = time, time = time,
member = members[index % members.size], member = members[index % members.size],
status = ReservationStatus.CONFIRMED status = ReservationStatus.CONFIRMED
) )
@ -733,7 +733,7 @@ class ReservationControllerTest(
fun createRequest( fun createRequest(
theme: ThemeEntity = ThemeFixture.create(), theme: ThemeEntity = ThemeFixture.create(),
time: ReservationTimeEntity = ReservationTimeFixture.create(), time: TimeEntity = TimeFixture.create(),
): ReservationRequest { ): ReservationRequest {
lateinit var reservationRequest: ReservationRequest lateinit var reservationRequest: ReservationRequest

View File

@ -13,28 +13,28 @@ import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
import roomescape.common.config.JacksonConfig import roomescape.common.config.JacksonConfig
import roomescape.common.exception.ErrorType import roomescape.common.exception.ErrorType
import roomescape.reservation.business.ReservationTimeService import roomescape.reservation.business.TimeService
import roomescape.reservation.infrastructure.persistence.ReservationRepository import roomescape.reservation.infrastructure.persistence.ReservationRepository
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity import roomescape.reservation.infrastructure.persistence.TimeEntity
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository import roomescape.reservation.infrastructure.persistence.TimeRepository
import roomescape.util.ReservationFixture import roomescape.util.ReservationFixture
import roomescape.util.ReservationTimeFixture import roomescape.util.TimeFixture
import roomescape.util.RoomescapeApiTest import roomescape.util.RoomescapeApiTest
import roomescape.util.ThemeFixture import roomescape.util.ThemeFixture
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
@WebMvcTest(ReservationTimeController::class) @WebMvcTest(TimeController::class)
@Import(JacksonConfig::class) @Import(JacksonConfig::class)
class ReservationTimeControllerTest( class TimeControllerTest(
val mockMvc: MockMvc, val mockMvc: MockMvc,
) : RoomescapeApiTest() { ) : RoomescapeApiTest() {
@SpykBean @SpykBean
private lateinit var reservationTimeService: ReservationTimeService private lateinit var timeService: TimeService
@MockkBean @MockkBean
private lateinit var reservationTimeRepository: ReservationTimeRepository private lateinit var timeRepository: TimeRepository
@MockkBean @MockkBean
private lateinit var reservationRepository: ReservationRepository private lateinit var reservationRepository: ReservationRepository
@ -50,10 +50,10 @@ class ReservationTimeControllerTest(
Then("정상 응답") { Then("정상 응답") {
every { every {
reservationTimeRepository.findAll() timeRepository.findAll()
} returns listOf( } returns listOf(
ReservationTimeFixture.create(id = 1L), TimeFixture.create(id = 1L),
ReservationTimeFixture.create(id = 2L) TimeFixture.create(id = 2L)
) )
runGetTest( runGetTest(
@ -95,7 +95,7 @@ class ReservationTimeControllerTest(
loginAsAdmin() loginAsAdmin()
} }
val time = LocalTime.of(10, 0) val time = LocalTime.of(10, 0)
val request = ReservationTimeRequest(startAt = time) val request = TimeCreateRequest(startAt = time)
Then("시간 형식이 HH:mm이 아니거나, 범위를 벗어나면 400 응답") { Then("시간 형식이 HH:mm이 아니거나, 범위를 벗어나면 400 응답") {
listOf( listOf(
@ -115,8 +115,8 @@ class ReservationTimeControllerTest(
Then("정상 응답") { Then("정상 응답") {
every { every {
reservationTimeService.addTime(request) timeService.create(request)
} returns ReservationTimeResponse(id = 1, startAt = time) } returns TimeCreateResponse(id = 1, startAt = time)
runPostTest( runPostTest(
mockMvc = mockMvc, mockMvc = mockMvc,
@ -135,7 +135,7 @@ class ReservationTimeControllerTest(
Then("동일한 시간이 존재하면 409 응답") { Then("동일한 시간이 존재하면 409 응답") {
every { every {
reservationTimeRepository.existsByStartAt(time) timeRepository.existsByStartAt(time)
} returns true } returns true
runPostTest( runPostTest(
@ -160,7 +160,7 @@ class ReservationTimeControllerTest(
runPostTest( runPostTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,
body = ReservationTimeFixture.create(), body = TimeFixture.create(),
log = true log = true
) { ) {
status { is3xxRedirection() } status { is3xxRedirection() }
@ -180,7 +180,7 @@ class ReservationTimeControllerTest(
Then("정상 응답") { Then("정상 응답") {
every { every {
reservationTimeService.removeTimeById(1L) timeService.deleteById(1L)
} returns Unit } returns Unit
runDeleteTest( runDeleteTest(
@ -195,7 +195,7 @@ class ReservationTimeControllerTest(
Then("없는 시간을 조회하면 400 응답") { Then("없는 시간을 조회하면 400 응답") {
val id = 1L val id = 1L
every { every {
reservationTimeRepository.findByIdOrNull(id) timeRepository.findByIdOrNull(id)
} returns null } returns null
runDeleteTest( runDeleteTest(
@ -206,7 +206,7 @@ class ReservationTimeControllerTest(
status { isBadRequest() } status { isBadRequest() }
content { content {
contentType(MediaType.APPLICATION_JSON) contentType(MediaType.APPLICATION_JSON)
jsonPath("$.errorType") { value(ErrorType.RESERVATION_TIME_NOT_FOUND.name) } jsonPath("$.errorType") { value(ErrorType.TIME_NOT_FOUND.name) }
} }
} }
} }
@ -214,11 +214,11 @@ class ReservationTimeControllerTest(
Then("예약이 있는 시간을 삭제하면 409 응답") { Then("예약이 있는 시간을 삭제하면 409 응답") {
val id = 1L val id = 1L
every { every {
reservationTimeRepository.findByIdOrNull(id) timeRepository.findByIdOrNull(id)
} returns ReservationTimeFixture.create(id = id) } returns TimeFixture.create(id = id)
every { every {
reservationRepository.findByReservationTime(any()) reservationRepository.findByTime(any())
} returns listOf(ReservationFixture.create()) } returns listOf(ReservationFixture.create())
runDeleteTest( runDeleteTest(
@ -258,13 +258,13 @@ class ReservationTimeControllerTest(
val themeId = 1L val themeId = 1L
When("저장된 예약 시간이 있으면") { When("저장된 예약 시간이 있으면") {
val times: List<ReservationTimeEntity> = listOf( val times: List<TimeEntity> = listOf(
ReservationTimeFixture.create(id = 1L, startAt = LocalTime.of(10, 0)), TimeFixture.create(id = 1L, startAt = LocalTime.of(10, 0)),
ReservationTimeFixture.create(id = 2L, startAt = LocalTime.of(11, 0)) TimeFixture.create(id = 2L, startAt = LocalTime.of(11, 0))
) )
every { every {
reservationTimeRepository.findAll() timeRepository.findAll()
} returns times } returns times
Then("그 시간과, 해당 날짜와 테마에 대한 예약 여부가 담긴 목록을 응답") { Then("그 시간과, 해당 날짜와 테마에 대한 예약 여부가 담긴 목록을 응답") {
@ -276,7 +276,7 @@ class ReservationTimeControllerTest(
id = 1L, id = 1L,
date = date, date = date,
theme = ThemeFixture.create(id = themeId), theme = ThemeFixture.create(id = themeId),
reservationTime = times[0] time = times[0]
) )
) )
@ -289,15 +289,15 @@ class ReservationTimeControllerTest(
content { content {
contentType(MediaType.APPLICATION_JSON) contentType(MediaType.APPLICATION_JSON)
} }
}.andReturn().readValue(ReservationTimeInfosResponse::class.java) }.andReturn().readValue(TimeWithAvailabilityListResponse::class.java)
assertSoftly(response.times) { assertSoftly(response.times) {
this shouldHaveSize times.size this shouldHaveSize times.size
this[0].id shouldBe times[0].id this[0].id shouldBe times[0].id
this[0].alreadyBooked shouldBe true this[0].isAvailable shouldBe false
this[1].id shouldBe times[1].id this[1].id shouldBe times[1].id
this[1].alreadyBooked shouldBe false this[1].isAvailable shouldBe true
} }
} }
} }

View File

@ -3,11 +3,11 @@ package roomescape.theme.util
import jakarta.persistence.EntityManager import jakarta.persistence.EntityManager
import roomescape.member.infrastructure.persistence.MemberEntity import roomescape.member.infrastructure.persistence.MemberEntity
import roomescape.reservation.infrastructure.persistence.ReservationStatus import roomescape.reservation.infrastructure.persistence.ReservationStatus
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity import roomescape.reservation.infrastructure.persistence.TimeEntity
import roomescape.theme.infrastructure.persistence.ThemeEntity import roomescape.theme.infrastructure.persistence.ThemeEntity
import roomescape.util.MemberFixture import roomescape.util.MemberFixture
import roomescape.util.ReservationFixture import roomescape.util.ReservationFixture
import roomescape.util.ReservationTimeFixture import roomescape.util.TimeFixture
import roomescape.util.ThemeFixture import roomescape.util.ThemeFixture
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
@ -23,7 +23,7 @@ object TestThemeCreateUtil {
val member: MemberEntity = MemberFixture.create().also { entityManager.persist(it) } val member: MemberEntity = MemberFixture.create().also { entityManager.persist(it) }
for (i in 1..reservedCount) { for (i in 1..reservedCount) {
val time: ReservationTimeEntity = ReservationTimeFixture.create( val time: TimeEntity = TimeFixture.create(
startAt = LocalTime.now().plusMinutes(i.toLong()) startAt = LocalTime.now().plusMinutes(i.toLong())
).also { entityManager.persist(it) } ).also { entityManager.persist(it) }
@ -31,7 +31,7 @@ object TestThemeCreateUtil {
date = date, date = date,
theme = themeEntity, theme = themeEntity,
member = member, member = member,
reservationTime = time, time = time,
status = ReservationStatus.CONFIRMED status = ReservationStatus.CONFIRMED
).also { entityManager.persist(it) } ).also { entityManager.persist(it) }
} }

View File

@ -12,7 +12,7 @@ import roomescape.payment.web.PaymentCancelRequest
import roomescape.payment.web.PaymentCancelResponse import roomescape.payment.web.PaymentCancelResponse
import roomescape.reservation.infrastructure.persistence.ReservationEntity import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.reservation.infrastructure.persistence.ReservationStatus import roomescape.reservation.infrastructure.persistence.ReservationStatus
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity import roomescape.reservation.infrastructure.persistence.TimeEntity
import roomescape.reservation.web.ReservationRequest import roomescape.reservation.web.ReservationRequest
import roomescape.reservation.web.WaitingRequest import roomescape.reservation.web.WaitingRequest
import roomescape.theme.infrastructure.persistence.ThemeEntity import roomescape.theme.infrastructure.persistence.ThemeEntity
@ -54,11 +54,11 @@ object MemberFixture {
) )
} }
object ReservationTimeFixture { object TimeFixture {
fun create( fun create(
id: Long? = null, id: Long? = null,
startAt: LocalTime = LocalTime.now().plusHours(1), startAt: LocalTime = LocalTime.now().plusHours(1),
): ReservationTimeEntity = ReservationTimeEntity(id, startAt) ): TimeEntity = TimeEntity(id, startAt)
} }
object ThemeFixture { object ThemeFixture {
@ -75,10 +75,10 @@ object ReservationFixture {
id: Long? = null, id: Long? = null,
date: LocalDate = LocalDate.now().plusWeeks(1), date: LocalDate = LocalDate.now().plusWeeks(1),
theme: ThemeEntity = ThemeFixture.create(), theme: ThemeEntity = ThemeFixture.create(),
reservationTime: ReservationTimeEntity = ReservationTimeFixture.create(), time: TimeEntity = TimeFixture.create(),
member: MemberEntity = MemberFixture.create(), member: MemberEntity = MemberFixture.create(),
status: ReservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED status: ReservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
): ReservationEntity = ReservationEntity(id, date, reservationTime, theme, member, status) ): ReservationEntity = ReservationEntity(id, date, time, theme, member, status)
fun createRequest( fun createRequest(
date: LocalDate = LocalDate.now().plusWeeks(1), date: LocalDate = LocalDate.now().plusWeeks(1),