generated from pricelees/issue-pr-template
188 lines
8.1 KiB
Kotlin
188 lines
8.1 KiB
Kotlin
package roomescape.reservation.business
|
|
|
|
import com.github.f4b6a3.tsid.TsidFactory
|
|
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 roomescape.common.config.next
|
|
import roomescape.common.dto.CurrentUserContext
|
|
import roomescape.common.dto.PrincipalType
|
|
import roomescape.common.util.DateUtils
|
|
import roomescape.member.business.UserService
|
|
import roomescape.member.web.UserContactRetrieveResponse
|
|
import roomescape.payment.business.PaymentService
|
|
import roomescape.payment.web.PaymentRetrieveResponse
|
|
import roomescape.reservation.exception.ReservationErrorCode
|
|
import roomescape.reservation.exception.ReservationException
|
|
import roomescape.reservation.infrastructure.persistence.*
|
|
import roomescape.reservation.web.*
|
|
import roomescape.schedule.business.ScheduleService
|
|
import roomescape.schedule.infrastructure.persistence.ScheduleStatus
|
|
import roomescape.schedule.web.ScheduleSummaryResponse
|
|
import roomescape.schedule.web.ScheduleUpdateRequest
|
|
import roomescape.theme.business.ThemeService
|
|
import roomescape.theme.web.ThemeInfoRetrieveResponse
|
|
import java.time.LocalDate
|
|
import java.time.LocalDateTime
|
|
|
|
private val log: KLogger = KotlinLogging.logger {}
|
|
|
|
@Service
|
|
class ReservationService(
|
|
private val reservationRepository: ReservationRepository,
|
|
private val reservationValidator: ReservationValidator,
|
|
private val scheduleService: ScheduleService,
|
|
private val userService: UserService,
|
|
private val themeService: ThemeService,
|
|
private val canceledReservationRepository: CanceledReservationRepository,
|
|
private val tsidFactory: TsidFactory,
|
|
private val paymentService: PaymentService
|
|
) {
|
|
|
|
@Transactional
|
|
fun createPendingReservation(
|
|
user: CurrentUserContext,
|
|
request: PendingReservationCreateRequest
|
|
): PendingReservationCreateResponse {
|
|
log.info { "[ReservationService.createPendingReservation] Pending 예약 생성 시작: schedule=${request.scheduleId}" }
|
|
|
|
validateCanCreate(request)
|
|
|
|
val reservation: ReservationEntity = request.toEntity(id = tsidFactory.next(), userId = user.id)
|
|
|
|
return PendingReservationCreateResponse(reservationRepository.save(reservation).id)
|
|
.also { "[ReservationService.createPendingReservation] Pending 예약 생성 완료: reservationId=${it}, schedule=${request.scheduleId}" }
|
|
}
|
|
|
|
@Transactional
|
|
fun confirmReservation(id: Long) {
|
|
log.info { "[ReservationService.confirmReservation] Pending 예약 확정 시작: reservationId=${id}" }
|
|
val reservation: ReservationEntity = findOrThrow(id)
|
|
|
|
run {
|
|
reservation.confirm()
|
|
scheduleService.updateSchedule(
|
|
reservation.scheduleId,
|
|
ScheduleUpdateRequest(status = ScheduleStatus.RESERVED)
|
|
)
|
|
}.also {
|
|
log.info { "[ReservationService.confirmReservation] Pending 예약 확정 완료: reservationId=${id}" }
|
|
}
|
|
}
|
|
|
|
@Transactional
|
|
fun cancelReservation(user: CurrentUserContext, reservationId: Long, request: ReservationCancelRequest) {
|
|
log.info { "[ReservationService.cancelReservation] 예약 취소 시작: userId=${user.id}, reservationId=${reservationId}" }
|
|
|
|
val reservation: ReservationEntity = findOrThrow(reservationId)
|
|
|
|
run {
|
|
scheduleService.updateSchedule(
|
|
reservation.scheduleId,
|
|
ScheduleUpdateRequest(status = ScheduleStatus.AVAILABLE)
|
|
)
|
|
saveCanceledReservation(user, reservation, request.cancelReason)
|
|
reservation.cancel()
|
|
}.also {
|
|
log.info { "[ReservationService.cancelReservation] 예약 취소 완료: reservationId=${reservationId}" }
|
|
}
|
|
}
|
|
|
|
@Transactional(readOnly = true)
|
|
fun findUserSummaryReservation(user: CurrentUserContext): ReservationSummaryRetrieveListResponse {
|
|
log.info { "[ReservationService.findSummaryByMemberId] 예약 조회 시작: userId=${user.id}" }
|
|
|
|
val reservations: List<ReservationEntity> = reservationRepository.findAllByUserId(user.id)
|
|
|
|
return ReservationSummaryRetrieveListResponse(reservations.map {
|
|
val schedule: ScheduleSummaryResponse = scheduleService.findSummaryById(it.scheduleId)
|
|
val theme: ThemeInfoRetrieveResponse = themeService.findSummaryById(schedule.themeId)
|
|
|
|
ReservationSummaryRetrieveResponse(
|
|
id = it.id,
|
|
themeName = theme.name,
|
|
date = schedule.date,
|
|
startAt = schedule.time,
|
|
status = it.status
|
|
)
|
|
}).also {
|
|
log.info { "[ReservationService.findSummaryByMemberId] ${it.reservations.size}개의 예약 조회 완료: userId=${user.id}" }
|
|
}
|
|
}
|
|
|
|
@Transactional(readOnly = true)
|
|
fun findDetailById(id: Long): ReservationDetailRetrieveResponse {
|
|
log.info { "[ReservationService.findDetailById] 예약 상세 조회 시작: reservationId=${id}" }
|
|
|
|
val reservation: ReservationEntity = findOrThrow(id)
|
|
val user: UserContactRetrieveResponse = userService.findContactById(reservation.userId)
|
|
val paymentDetail: PaymentRetrieveResponse = paymentService.findDetailByReservationId(id)
|
|
|
|
return reservation.toReservationDetailRetrieveResponse(
|
|
user = user,
|
|
payment = paymentDetail
|
|
).also {
|
|
log.info { "[ReservationService.findDetailById] 예약 상세 조회 완료: reservationId=${id}" }
|
|
}
|
|
}
|
|
|
|
@Transactional(readOnly = true)
|
|
fun findMostReservedThemeIds(count: Int): MostReservedThemeIdListResponse {
|
|
log.info { "[ReservationService.findMostReservedThemeIds] 인기 테마 조회 시작: count=$count" }
|
|
val previousWeekSunday = DateUtils.getSundayOfPreviousWeek(LocalDate.now())
|
|
val previousWeekSaturday = previousWeekSunday.plusDays(6)
|
|
|
|
val themeIds: List<Long> = reservationRepository.findMostReservedThemeIds(
|
|
dateFrom = previousWeekSunday,
|
|
dateTo = previousWeekSaturday,
|
|
count = count
|
|
)
|
|
|
|
return MostReservedThemeIdListResponse(themeIds = themeIds).also {
|
|
log.info { "[ReservationService.findMostReservedThemeIds] 인기 테마 조회 완료: count=${it.themeIds.size}" }
|
|
}
|
|
}
|
|
|
|
private fun findOrThrow(id: Long): ReservationEntity {
|
|
log.info { "[ReservationService.findOrThrow] 예약 조회 시작: reservationId=${id}" }
|
|
|
|
return reservationRepository.findByIdOrNull(id)
|
|
?.also { log.info { "[ReservationService.findOrThrow] 예약 조회 완료: reservationId=${id}" } }
|
|
?: run {
|
|
log.warn { "[ReservationService.findOrThrow] 예약 조회 실패: reservationId=${id}" }
|
|
throw ReservationException(ReservationErrorCode.RESERVATION_NOT_FOUND)
|
|
}
|
|
}
|
|
|
|
private fun saveCanceledReservation(
|
|
user: CurrentUserContext,
|
|
reservation: ReservationEntity,
|
|
cancelReason: String
|
|
) {
|
|
if (user.type != PrincipalType.ADMIN && reservation.userId != user.id) {
|
|
log.warn { "[ReservationService.createCanceledPayment] 예약자 본인 또는 관리자가 아닌 회원의 취소 요청: reservationId=${reservation.id}, userId=${user.id}" }
|
|
throw ReservationException(ReservationErrorCode.NO_PERMISSION_TO_CANCEL_RESERVATION)
|
|
}
|
|
|
|
CanceledReservationEntity(
|
|
id = tsidFactory.next(),
|
|
reservationId = reservation.id,
|
|
canceledBy = user.id,
|
|
cancelReason = cancelReason,
|
|
canceledAt = LocalDateTime.now(),
|
|
status = CanceledReservationStatus.PROCESSING
|
|
).also {
|
|
canceledReservationRepository.save(it)
|
|
}
|
|
}
|
|
|
|
private fun validateCanCreate(request: PendingReservationCreateRequest) {
|
|
val schedule = scheduleService.findSummaryById(request.scheduleId)
|
|
val theme = themeService.findSummaryById(schedule.themeId)
|
|
|
|
reservationValidator.validateCanCreate(schedule, theme, request)
|
|
}
|
|
}
|