generated from pricelees/issue-pr-template
[#41] 예약 스키마 재정의 #42
@ -0,0 +1,163 @@
|
||||
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.member.business.MemberService
|
||||
import roomescape.member.infrastructure.persistence.Role
|
||||
import roomescape.member.web.MemberSummaryRetrieveResponse
|
||||
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.ThemeServiceV2
|
||||
import roomescape.theme.web.ThemeRetrieveResponseV2
|
||||
import java.time.LocalDateTime
|
||||
|
||||
private val log: KLogger = KotlinLogging.logger {}
|
||||
|
||||
@Service
|
||||
class ReservationService(
|
||||
private val reservationRepository: ReservationRepository,
|
||||
private val scheduleService: ScheduleService,
|
||||
private val memberService: MemberService,
|
||||
private val themeService: ThemeServiceV2,
|
||||
private val canceledReservationRepository: CanceledReservationRepository,
|
||||
private val tsidFactory: TsidFactory,
|
||||
private val paymentService: PaymentService
|
||||
) {
|
||||
|
||||
@Transactional
|
||||
fun createPendingReservation(
|
||||
memberId: Long,
|
||||
request: PendingReservationCreateRequest
|
||||
): PendingReservationCreateResponse {
|
||||
log.info { "[ReservationService.createPendingReservation] Pending 예약 생성 시작: schedule=${request.scheduleId}" }
|
||||
|
||||
val reservation: ReservationEntity = request.toEntity(
|
||||
id = tsidFactory.next(),
|
||||
memberId = memberId
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
if (reservation.status != ReservationStatus.PENDING) {
|
||||
log.warn { "[ReservationService.confirmReservation] 예약이 Pending 상태가 아님: reservationId=${id}, status=${reservation.status}" }
|
||||
}
|
||||
|
||||
run {
|
||||
reservation.confirm()
|
||||
scheduleService.updateSchedule(
|
||||
reservation.scheduleId,
|
||||
ScheduleUpdateRequest(status = ScheduleStatus.RESERVED)
|
||||
)
|
||||
}.also {
|
||||
log.info { "[ReservationService.confirmReservation] Pending 예약 확정 완료: reservationId=${id}" }
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun cancelReservation(memberId: Long, reservationId: Long, request: ReservationCancelRequest) {
|
||||
log.info { "[ReservationService.cancelReservation] 예약 취소 시작: memberId=${memberId}, reservationId=${reservationId}" }
|
||||
|
||||
val reservation: ReservationEntity = findOrThrow(reservationId)
|
||||
val member: MemberSummaryRetrieveResponse = memberService.findSummaryById(memberId)
|
||||
|
||||
run {
|
||||
scheduleService.updateSchedule(
|
||||
reservation.scheduleId,
|
||||
ScheduleUpdateRequest(status = ScheduleStatus.AVAILABLE)
|
||||
)
|
||||
saveCanceledReservation(member, reservation, request.cancelReason)
|
||||
reservation.cancel()
|
||||
}.also {
|
||||
log.info { "[ReservationService.cancelReservation] 예약 취소 완료: reservationId=${reservationId}" }
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun findSummaryByMemberId(memberId: Long): ReservationSummaryRetrieveListResponse {
|
||||
log.info { "[ReservationService.findSummaryByMemberId] 예약 조회 시작: memberId=${memberId}" }
|
||||
|
||||
val reservations: List<ReservationEntity> = reservationRepository.findAllByMemberId(memberId)
|
||||
|
||||
return ReservationSummaryRetrieveListResponse(reservations.map {
|
||||
val schedule: ScheduleSummaryResponse = scheduleService.findSummaryById(it.scheduleId)
|
||||
val theme: ThemeRetrieveResponseV2 = themeService.findById(schedule.themeId)
|
||||
|
||||
ReservationSummaryRetrieveResponse(
|
||||
id = it.id,
|
||||
themeName = theme.name,
|
||||
date = schedule.date,
|
||||
startAt = schedule.time,
|
||||
status = it.status
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun findDetailById(id: Long): ReservationDetailRetrieveResponse {
|
||||
log.info { "[ReservationService.findDetailById] 예약 상세 조회 시작: reservationId=${id}" }
|
||||
|
||||
val reservation: ReservationEntity = findOrThrow(id)
|
||||
val member: MemberSummaryRetrieveResponse = memberService.findSummaryById(reservation.memberId)
|
||||
val paymentDetail: PaymentRetrieveResponse = paymentService.findDetailByReservationId(id)
|
||||
|
||||
return reservation.toReservationDetailRetrieveResponse(
|
||||
member = member,
|
||||
payment = paymentDetail
|
||||
).also {
|
||||
log.info { "[ReservationService.findDetailById] 예약 상세 조회 완료: reservationId=${id}" }
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
member: MemberSummaryRetrieveResponse,
|
||||
reservation: ReservationEntity,
|
||||
cancelReason: String
|
||||
) {
|
||||
if (member.role != Role.ADMIN && reservation.memberId != member.id) {
|
||||
log.warn { "[ReservationService.createCanceledPayment] 예약자 본인 또는 관리자가 아닌 회원의 취소 요청: reservationId=${reservation.id}, memberId=${member.id}" }
|
||||
throw ReservationException(ReservationErrorCode.NO_PERMISSION_TO_CANCEL_RESERVATION)
|
||||
}
|
||||
|
||||
CanceledReservationEntity(
|
||||
id = tsidFactory.next(),
|
||||
reservationId = reservation.id,
|
||||
canceledBy = member.id,
|
||||
cancelReason = cancelReason,
|
||||
canceledAt = LocalDateTime.now(),
|
||||
status = CanceledReservationStatus.PROCESSING
|
||||
).also {
|
||||
canceledReservationRepository.save(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,75 +1,8 @@
|
||||
package roomescape.reservation.infrastructure.persistence
|
||||
|
||||
import org.springframework.data.jpa.domain.Specification
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor
|
||||
import org.springframework.data.jpa.repository.Modifying
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.query.Param
|
||||
import roomescape.reservation.web.MyReservationRetrieveResponse
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
import roomescape.time.infrastructure.persistence.TimeEntity
|
||||
import java.time.LocalDate
|
||||
|
||||
interface ReservationRepository
|
||||
: JpaRepository<ReservationEntity, Long>, JpaSpecificationExecutor<ReservationEntity> {
|
||||
fun findAllByTime(time: TimeEntity): List<ReservationEntity>
|
||||
fun existsByTime(time: TimeEntity): Boolean
|
||||
interface ReservationRepository : JpaRepository<ReservationEntity, Long> {
|
||||
|
||||
fun findByDateAndThemeId(date: LocalDate, themeId: Long): List<ReservationEntity>
|
||||
|
||||
@Modifying
|
||||
@Query(
|
||||
"""
|
||||
UPDATE ReservationEntity r
|
||||
SET r.status = :status
|
||||
WHERE r._id = :_id
|
||||
"""
|
||||
)
|
||||
fun updateStatusByReservationId(
|
||||
@Param(value = "_id") reservationId: Long,
|
||||
@Param(value = "status") statusForChange: ReservationStatus
|
||||
): Int
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM ReservationEntity r2
|
||||
WHERE r2._id = :_id
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM ReservationEntity r
|
||||
WHERE r.theme._id = r2.theme._id
|
||||
AND r.time._id = r2.time._id
|
||||
AND r.date = r2.date
|
||||
AND r.status != 'WAITING'
|
||||
)
|
||||
)
|
||||
"""
|
||||
)
|
||||
fun isExistConfirmedReservation(@Param("_id") reservationId: Long): Boolean
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT new roomescape.reservation.web.MyReservationRetrieveResponse(
|
||||
r._id,
|
||||
t.name,
|
||||
r.date,
|
||||
r.time.startAt,
|
||||
r.status,
|
||||
(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.totalAmount
|
||||
)
|
||||
FROM ReservationEntity r
|
||||
JOIN r.theme t
|
||||
LEFT JOIN PaymentEntity p
|
||||
ON p.reservation = r
|
||||
WHERE r.member._id = :memberId
|
||||
"""
|
||||
)
|
||||
fun findAllByMemberId(memberId: Long): List<MyReservationRetrieveResponse>
|
||||
fun findAllByDateAndTheme(date: LocalDate, theme: ThemeEntity): List<ReservationEntity>
|
||||
|
||||
fun findAllByMember_Id(memberId: Long): List<ReservationEntity>
|
||||
fun findAllByMemberId(memberId: Long): List<ReservationEntity>
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user