[#16] Reservation 도메인 코드 코틀린 마이그레이션 #17

Merged
pricelees merged 40 commits from refactor/#16 into main 2025-07-21 12:08:56 +00:00
4 changed files with 82 additions and 92 deletions
Showing only changes of commit b0290e65ff - Show all commits

View File

@ -1,103 +1,87 @@
package roomescape.reservation.business; package roomescape.reservation.business
import java.time.LocalDate; import org.springframework.data.repository.findByIdOrNull
import java.util.List; import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.http.HttpStatus; import org.springframework.transaction.annotation.Transactional
import org.springframework.stereotype.Service; import roomescape.common.exception.ErrorType
import org.springframework.transaction.annotation.Transactional; import roomescape.common.exception.RoomescapeException
import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.common.exception.ErrorType; import roomescape.reservation.infrastructure.persistence.ReservationRepository
import roomescape.common.exception.RoomescapeException; import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity
import roomescape.reservation.infrastructure.persistence.ReservationEntity; import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository
import roomescape.reservation.infrastructure.persistence.ReservationRepository; import roomescape.reservation.web.*
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity; import java.time.LocalDate
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository;
import roomescape.reservation.web.ReservationTimeInfoResponse;
import roomescape.reservation.web.ReservationTimeInfosResponse;
import roomescape.reservation.web.ReservationTimeRequest;
import roomescape.reservation.web.ReservationTimeResponse;
import roomescape.reservation.web.ReservationTimesResponse;
@Service @Service
@Transactional class ReservationTimeService(
public class ReservationTimeService { private val reservationTimeRepository: ReservationTimeRepository,
private val reservationRepository: ReservationRepository
private final ReservationTimeRepository reservationTimeRepository;
private final ReservationRepository reservationRepository;
public ReservationTimeService(
ReservationTimeRepository reservationTimeRepository,
ReservationRepository reservationRepository
) { ) {
this.reservationTimeRepository = reservationTimeRepository; @Transactional(readOnly = true)
this.reservationRepository = reservationRepository; 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 {
val response = reservationTimeRepository.findAll()
.map { it.toResponse() }
return ReservationTimesResponse(response)
}
@Transactional
fun addTime(reservationTimeRequest: ReservationTimeRequest): ReservationTimeResponse {
validateIsDuplicatedTimeExists(reservationTimeRequest)
return ReservationTimeEntity(startAt = reservationTimeRequest.startAt)
.also { reservationTimeRepository.save(it) }
.toResponse()
}
private fun validateIsDuplicatedTimeExists(reservationTimeRequest: ReservationTimeRequest) {
reservationTimeRepository.findByStartAt(reservationTimeRequest.startAt)
.also {
if (it.isNotEmpty()) {
throw RoomescapeException(
ErrorType.TIME_DUPLICATED, "[startAt: $it]", HttpStatus.CONFLICT
)
}
}
}
@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) @Transactional(readOnly = true)
public ReservationTimeEntity findTimeById(Long id) { fun findAllAvailableTimesByDateAndTheme(date: LocalDate, themeId: Long): ReservationTimeInfosResponse {
return reservationTimeRepository.findById(id) val allTimes = reservationTimeRepository.findAll()
.orElseThrow(() -> new RoomescapeException(ErrorType.RESERVATION_TIME_NOT_FOUND, val reservations: List<ReservationEntity> = reservationRepository.findByThemeId(themeId)
String.format("[reservationTimeId: %d]", id), HttpStatus.BAD_REQUEST));
return ReservationTimeInfosResponse(allTimes
.map { time ->
ReservationTimeInfoResponse(
time.id!!,
time.startAt,
reservations.any { reservation -> reservation.hasSameDateTime(date, time) }
)
} }
)
@Transactional(readOnly = true)
public ReservationTimesResponse findAllTimes() {
List<ReservationTimeResponse> response = reservationTimeRepository.findAll()
.stream()
.map(ReservationTimeResponse::from)
.toList();
return new ReservationTimesResponse(response);
}
public ReservationTimeResponse addTime(ReservationTimeRequest reservationTimeRequest) {
validateTimeDuplication(reservationTimeRequest);
ReservationTimeEntity reservationTime = reservationTimeRepository.save(
new ReservationTimeEntity(null, reservationTimeRequest.startAt)
);
return ReservationTimeResponse.from(reservationTime);
}
private void validateTimeDuplication(ReservationTimeRequest reservationTimeRequest) {
List<ReservationTimeEntity> duplicateReservationTimes = reservationTimeRepository.findByStartAt(
reservationTimeRequest.startAt);
if (!duplicateReservationTimes.isEmpty()) {
throw new RoomescapeException(ErrorType.TIME_DUPLICATED,
String.format("[startAt: %s]", reservationTimeRequest.startAt), HttpStatus.CONFLICT);
}
}
public void removeTimeById(Long id) {
ReservationTimeEntity reservationTime = findTimeById(id);
List<ReservationEntity> usingTimeReservations = reservationRepository.findByReservationTime(reservationTime);
if (!usingTimeReservations.isEmpty()) {
throw new RoomescapeException(ErrorType.TIME_IS_USED_CONFLICT, String.format("[timeId: %d]", id),
HttpStatus.CONFLICT);
}
reservationTimeRepository.deleteById(id);
}
@Transactional(readOnly = true)
public ReservationTimeInfosResponse findAllAvailableTimesByDateAndTheme(LocalDate date, Long themeId) {
List<ReservationTimeEntity> allTimes = reservationTimeRepository.findAll();
List<ReservationEntity> reservations = reservationRepository.findByThemeId(themeId);
List<ReservationTimeInfoResponse> response = allTimes.stream()
.map(time -> new ReservationTimeInfoResponse(time.getId(), time.getStartAt(),
isReservationBooked(reservations, date, time)))
.toList();
return new ReservationTimeInfosResponse(response);
}
private boolean isReservationBooked(List<ReservationEntity> reservations, LocalDate date,
ReservationTimeEntity time) {
return reservations.stream()
.anyMatch(reservation -> reservation.isSameDateAndTime(date, time));
} }
} }

View File

@ -33,7 +33,7 @@ class ReservationEntity(
) { ) {
@JsonIgnore @JsonIgnore
fun isSameDateAndTime(date: LocalDate?, time: ReservationTimeEntity): Boolean { fun hasSameDateTime(date: LocalDate?, time: ReservationTimeEntity): Boolean {
return this.date == date && time.startAt == this.reservationTime.startAt return this.date == date && time.startAt == this.reservationTime.startAt
} }

View File

@ -111,3 +111,7 @@ data class ReservationTimeResponse(
} }
} }
} }
fun ReservationTimeEntity.toResponse(): ReservationTimeResponse = ReservationTimeResponse(
this.id!!, this.startAt
)

View File

@ -61,7 +61,8 @@ class ReservationTimeServiceTest {
new ReservationTimeEntity(null, LocalTime.of(12, 30))); new ReservationTimeEntity(null, LocalTime.of(12, 30)));
// when // when
Long invalidTimeId = saved.getId() + 1; assert saved.getId() != null;
long invalidTimeId = saved.getId() + 1;
// when & then // when & then
assertThatThrownBy(() -> reservationTimeService.findTimeById(invalidTimeId)) assertThatThrownBy(() -> reservationTimeService.findTimeById(invalidTimeId))
@ -85,6 +86,7 @@ class ReservationTimeServiceTest {
ReservationStatus.CONFIRMED)); ReservationStatus.CONFIRMED));
// then // then
assert reservationTime.getId() != null;
assertThatThrownBy(() -> reservationTimeService.removeTimeById(reservationTime.getId())) assertThatThrownBy(() -> reservationTimeService.removeTimeById(reservationTime.getId()))
.isInstanceOf(RoomescapeException.class); .isInstanceOf(RoomescapeException.class);
} }