245 lines
8.5 KiB
Kotlin

package roomescape.reservation.business
import org.springframework.data.jpa.domain.Specification
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.member.business.MemberService
import roomescape.reservation.infrastructure.persistence.*
import roomescape.reservation.web.*
import roomescape.theme.business.ThemeService
import java.time.LocalDate
import java.time.LocalDateTime
@Service
@Transactional
class ReservationService(
private val reservationRepository: ReservationRepository,
private val reservationTimeService: ReservationTimeService,
private val memberService: MemberService,
private val themeService: ThemeService,
) {
@Transactional(readOnly = true)
fun findAllReservations(): ReservationsResponse {
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
.confirmed()
.build()
return ReservationsResponse(findAllReservationByStatus(spec))
}
@Transactional(readOnly = true)
fun findAllWaiting(): ReservationsResponse {
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
.waiting()
.build()
return ReservationsResponse(findAllReservationByStatus(spec))
}
private fun findAllReservationByStatus(spec: Specification<ReservationEntity>): List<ReservationResponse> {
return reservationRepository.findAll(spec).map { it.toResponse() }
}
fun removeReservationById(reservationId: Long, memberId: Long) {
validateIsMemberAdmin(memberId)
reservationRepository.deleteById(reservationId)
}
fun addReservation(request: ReservationRequest, memberId: Long): ReservationEntity {
validateIsReservationExist(request.themeId, request.timeId, request.date)
return getReservationForSave(
request.timeId,
request.themeId,
request.date,
memberId,
ReservationStatus.CONFIRMED
).also {
reservationRepository.save(it)
}
}
fun addReservationByAdmin(request: AdminReservationRequest): ReservationResponse {
validateIsReservationExist(request.themeId, request.timeId, request.date)
return addReservationWithoutPayment(
request.themeId,
request.timeId,
request.date,
request.memberId,
ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
)
}
fun addWaiting(request: WaitingRequest, memberId: Long): ReservationResponse {
validateMemberAlreadyReserve(request.themeId, request.timeId, request.date, memberId)
return addReservationWithoutPayment(
request.themeId,
request.timeId,
request.date,
memberId,
ReservationStatus.WAITING
)
}
private fun addReservationWithoutPayment(
themeId: Long,
timeId: Long,
date: LocalDate,
memberId: Long,
status: ReservationStatus
): ReservationResponse = getReservationForSave(timeId, themeId, date, memberId, status)
.also {
reservationRepository.save(it)
}.toResponse()
private fun validateMemberAlreadyReserve(themeId: Long?, timeId: Long?, date: LocalDate?, memberId: Long?) {
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
.sameMemberId(memberId)
.sameThemeId(themeId)
.sameTimeId(timeId)
.sameDate(date)
.build()
if (reservationRepository.exists(spec)) {
throw RoomescapeException(ErrorType.HAS_RESERVATION_OR_WAITING, HttpStatus.BAD_REQUEST)
}
}
private fun validateIsReservationExist(themeId: Long, timeId: Long, date: LocalDate) {
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
.confirmed()
.sameThemeId(themeId)
.sameTimeId(timeId)
.sameDate(date)
.build()
if (reservationRepository.exists(spec)) {
throw RoomescapeException(ErrorType.RESERVATION_DUPLICATED, HttpStatus.CONFLICT)
}
}
private fun validateDateAndTime(
requestDate: LocalDate,
requestReservationTime: ReservationTimeEntity
) {
val now = LocalDateTime.now()
val request = LocalDateTime.of(requestDate, requestReservationTime.startAt)
if (request.isBefore(now)) {
throw RoomescapeException(
ErrorType.RESERVATION_PERIOD_IN_PAST,
"[now: $now | request: $request]",
HttpStatus.BAD_REQUEST
)
}
}
private fun getReservationForSave(
timeId: Long,
themeId: Long,
date: LocalDate,
memberId: Long,
status: ReservationStatus
): ReservationEntity {
val time = reservationTimeService.findTimeById(timeId)
val theme = themeService.findById(themeId)
val member = memberService.findById(memberId)
validateDateAndTime(date, time)
return ReservationEntity(
date = date,
reservationTime = time,
theme = theme,
member = member,
reservationStatus = status
)
}
@Transactional(readOnly = true)
fun findFilteredReservations(
themeId: Long?,
memberId: Long?,
dateFrom: LocalDate?,
dateTo: LocalDate?
): ReservationsResponse {
validateDateForSearch(dateFrom, dateTo)
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
.confirmed()
.sameThemeId(themeId)
.sameMemberId(memberId)
.dateStartFrom(dateFrom)
.dateEndAt(dateTo)
.build()
return ReservationsResponse(findAllReservationByStatus(spec))
}
private fun validateDateForSearch(startFrom: LocalDate?, endAt: LocalDate?) {
if (startFrom == null || endAt == null) {
return
}
if (startFrom.isAfter(endAt)) {
throw RoomescapeException(
ErrorType.INVALID_DATE_RANGE,
"[startFrom: $startFrom, endAt: $endAt", HttpStatus.BAD_REQUEST
)
}
}
@Transactional(readOnly = true)
fun findMemberReservations(memberId: Long): MyReservationsResponse {
return MyReservationsResponse(reservationRepository.findMyReservations(memberId))
}
fun approveWaiting(reservationId: Long, memberId: Long) {
validateIsMemberAdmin(memberId)
if (reservationRepository.isExistConfirmedReservation(reservationId)) {
throw RoomescapeException(ErrorType.RESERVATION_DUPLICATED, HttpStatus.CONFLICT)
}
reservationRepository.updateStatusByReservationId(reservationId, ReservationStatus.CONFIRMED_PAYMENT_REQUIRED)
}
fun cancelWaiting(reservationId: Long, memberId: Long) {
reservationRepository.findByIdOrNull(reservationId)?.takeIf {
it.isWaiting() && it.isSameMember(memberId)
}?.let {
reservationRepository.delete(it)
} ?: throw throwReservationNotFound(reservationId)
}
fun denyWaiting(reservationId: Long, memberId: Long) {
validateIsMemberAdmin(memberId)
reservationRepository.findByIdOrNull(reservationId)?.takeIf {
it.isWaiting()
}?.let {
reservationRepository.delete(it)
} ?: throw throwReservationNotFound(reservationId)
}
private fun validateIsMemberAdmin(memberId: Long) {
memberService.findById(memberId).takeIf {
it.isAdmin()
} ?: throw RoomescapeException(
ErrorType.PERMISSION_DOES_NOT_EXIST,
"[memberId: $memberId]",
HttpStatus.FORBIDDEN
)
}
private fun throwReservationNotFound(reservationId: Long?): RoomescapeException {
return RoomescapeException(
ErrorType.RESERVATION_NOT_FOUND,
"[reservationId: $reservationId]",
HttpStatus.NOT_FOUND
)
}
}