feat: 새로운 회원의 예약 조회 API 구현

This commit is contained in:
이상진 2025-08-18 15:56:58 +09:00
parent ba324464e9
commit 110d91a31e
4 changed files with 318 additions and 0 deletions

View File

@ -0,0 +1,54 @@
package roomescape.reservation.business
import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import roomescape.payment.implement.PaymentFinderV2
import roomescape.payment.infrastructure.persistence.v2.CanceledPaymentEntityV2
import roomescape.payment.infrastructure.persistence.v2.PaymentDetailEntity
import roomescape.payment.infrastructure.persistence.v2.PaymentEntityV2
import roomescape.reservation.implement.ReservationFinder
import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.reservation.web.ReservationDetailRetrieveResponse
import roomescape.reservation.web.ReservationSummaryRetrieveListResponse
import roomescape.reservation.web.toCancelDetailResponse
import roomescape.reservation.web.toPaymentDetailResponse
import roomescape.reservation.web.toReservationDetailRetrieveResponse
import roomescape.reservation.web.toRetrieveResponse
import roomescape.reservation.web.toSummaryListResponse
private val log: KLogger = KotlinLogging.logger {}
@Service
class MyReservationFindService(
private val reservationFinder: ReservationFinder,
private val paymentFinder: PaymentFinderV2
) {
@Transactional(readOnly = true)
fun findReservationsByMemberId(memberId: Long): ReservationSummaryRetrieveListResponse {
log.debug { "[ReservationFindServiceV2.findReservationsByMemberId] 시작: memberId=$memberId" }
return reservationFinder.findAllByMemberIdV2(memberId)
.toSummaryListResponse()
.also { log.info { "[ReservationFindServiceV2.findReservationsByMemberId] ${it.reservations.size}개의 예약 조회 완료: memberId=$memberId" } }
}
@Transactional(readOnly = true)
fun showReservationDetails(reservationId: Long): ReservationDetailRetrieveResponse {
log.debug { "[ReservationFindServiceV2.showReservationDetails] 시작: reservationId=$reservationId" }
val reservation: ReservationEntity = reservationFinder.findById(reservationId)
val payment: PaymentEntityV2 = paymentFinder.findPaymentByReservationId(reservationId)
val paymentDetail: PaymentDetailEntity = paymentFinder.findPaymentDetailByPaymentId(payment.id)
val canceledPayment: CanceledPaymentEntityV2? = paymentFinder.findCanceledPaymentByPaymentIdOrNull(payment.id)
return reservation.toReservationDetailRetrieveResponse(
payment = payment.toRetrieveResponse(detail = paymentDetail.toPaymentDetailResponse()),
cancellation = canceledPayment?.toCancelDetailResponse()
).also {
log.info { "[ReservationFindServiceV2.showReservationDetails] 예약 상세 조회 완료: reservationId=$reservationId" }
}
}
}

View File

@ -0,0 +1,33 @@
package roomescape.reservation.docs
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PathVariable
import roomescape.auth.web.support.LoginRequired
import roomescape.auth.web.support.MemberId
import roomescape.common.dto.response.CommonApiResponse
import roomescape.reservation.web.ReservationDetailRetrieveResponse
import roomescape.reservation.web.ReservationSummaryRetrieveListResponse
interface MyReservationAPI {
@LoginRequired
@Operation(summary = "내 예약 개요 조회", tags = ["로그인이 필요한 API"])
@ApiResponses(
ApiResponse(responseCode = "200", description = "성공"),
)
fun findAllMyReservations(
@MemberId @Parameter(hidden = true) memberId: Long
): ResponseEntity<CommonApiResponse<ReservationSummaryRetrieveListResponse>>
@LoginRequired
@Operation(summary = "예약 상세 조회", tags = ["로그인이 필요한 API"])
@ApiResponses(
ApiResponse(responseCode = "200", description = "성공"),
)
fun showReservationDetails(
@PathVariable("id") reservationId: Long
): ResponseEntity<CommonApiResponse<ReservationDetailRetrieveResponse>>
}

View File

@ -0,0 +1,35 @@
package roomescape.reservation.web
import io.swagger.v3.oas.annotations.Parameter
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController
import roomescape.auth.web.support.MemberId
import roomescape.common.dto.response.CommonApiResponse
import roomescape.reservation.business.MyReservationFindService
import roomescape.reservation.docs.MyReservationAPI
@RestController
class MyReservationController(
private val reservationFindService: MyReservationFindService
) : MyReservationAPI {
@GetMapping("/v2/reservations")
override fun findAllMyReservations(
@MemberId @Parameter(hidden=true) memberId: Long
): ResponseEntity<CommonApiResponse<ReservationSummaryRetrieveListResponse>> {
val response = reservationFindService.findReservationsByMemberId(memberId)
return ResponseEntity.ok(CommonApiResponse(response))
}
@GetMapping("/v2/reservations/{id}/details")
override fun showReservationDetails(
@PathVariable("id") reservationId: Long
): ResponseEntity<CommonApiResponse<ReservationDetailRetrieveResponse>> {
val response = reservationFindService.showReservationDetails(reservationId)
return ResponseEntity.ok(CommonApiResponse(response))
}
}

View File

@ -0,0 +1,196 @@
package roomescape.reservation.web
import roomescape.member.infrastructure.persistence.MemberEntity
import roomescape.payment.exception.PaymentErrorCode
import roomescape.payment.exception.PaymentException
import roomescape.payment.infrastructure.common.PaymentStatus
import roomescape.payment.infrastructure.persistence.v2.CanceledPaymentEntityV2
import roomescape.payment.infrastructure.persistence.v2.PaymentBankTransferDetailEntity
import roomescape.payment.infrastructure.persistence.v2.PaymentCardDetailEntity
import roomescape.payment.infrastructure.persistence.v2.PaymentDetailEntity
import roomescape.payment.infrastructure.persistence.v2.PaymentEasypayPrepaidDetailEntity
import roomescape.payment.infrastructure.persistence.v2.PaymentEntityV2
import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.reservation.infrastructure.persistence.ReservationStatus
import roomescape.reservation.web.PaymentDetailResponse.BankTransferDetailResponse
import roomescape.reservation.web.PaymentDetailResponse.CardDetailResponse
import roomescape.reservation.web.PaymentDetailResponse.EasyPayPrepaidDetailResponse
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.OffsetDateTime
import kotlin.Int
data class ReservationSummaryRetrieveResponse(
val id: Long,
val themeName: String,
val date: LocalDate,
val startAt: LocalTime,
val status: ReservationStatus
)
fun ReservationEntity.toReservationSummaryRetrieveResponse(): ReservationSummaryRetrieveResponse {
return ReservationSummaryRetrieveResponse(
id = this.id!!,
themeName = this.theme.name,
date = this.date,
startAt = this.time.startAt,
status = this.status
)
}
data class ReservationSummaryRetrieveListResponse(
val reservations: List<ReservationSummaryRetrieveResponse>
)
fun List<ReservationEntity>.toSummaryListResponse(): ReservationSummaryRetrieveListResponse {
return ReservationSummaryRetrieveListResponse(
reservations = this.map { it.toReservationSummaryRetrieveResponse() }
)
}
data class ReservationDetailRetrieveResponse(
val id: Long,
val user: UserDetailRetrieveResponse,
val themeName: String,
val date: LocalDate,
val startAt: LocalTime,
val applicationDateTime: LocalDateTime,
val payment: PaymentRetrieveResponse,
val cancellation: PaymentCancelDetailResponse? = null
)
data class UserDetailRetrieveResponse(
val id: Long,
val name: String,
val email: String
)
fun MemberEntity.toUserDetailRetrieveResponse(): UserDetailRetrieveResponse {
return UserDetailRetrieveResponse(
id = this.id!!,
name = this.name,
email = this.email
)
}
fun ReservationEntity.toReservationDetailRetrieveResponse(
payment: PaymentRetrieveResponse,
cancellation: PaymentCancelDetailResponse? = null
): ReservationDetailRetrieveResponse {
return ReservationDetailRetrieveResponse(
id = this.id!!,
user = this.member.toUserDetailRetrieveResponse(),
themeName = this.theme.name,
date = this.date,
startAt = this.time.startAt,
applicationDateTime = this.createdAt!!,
payment = payment,
cancellation = cancellation,
)
}
data class PaymentRetrieveResponse(
val orderId: String,
val totalAmount: Int,
val method: String,
val status: PaymentStatus,
val requestedAt: OffsetDateTime,
val approvedAt: OffsetDateTime,
val detail: PaymentDetailResponse,
)
fun PaymentEntityV2.toRetrieveResponse(detail: PaymentDetailResponse): PaymentRetrieveResponse {
return PaymentRetrieveResponse(
orderId = this.orderId,
totalAmount = this.totalAmount,
method = this.method.koreanName,
status = this.status,
requestedAt = this.requestedAt,
approvedAt = this.approvedAt,
detail = detail
)
}
sealed class PaymentDetailResponse {
data class CardDetailResponse(
val type: String = "CARD",
val issuerCode: String,
val cardType: String,
val ownerType: String,
val cardNumber: String,
val amount: Int,
val approvalNumber: String,
val installmentPlanMonths: Int,
val easypayProviderName: String?,
val easypayDiscountAmount: Int?,
) : PaymentDetailResponse()
data class BankTransferDetailResponse(
val type: String = "BANK_TRANSFER",
val bankName: String,
) : PaymentDetailResponse()
data class EasyPayPrepaidDetailResponse(
val type: String = "EASYPAY_PREPAID",
val providerName: String,
val amount: Int,
val discountAmount: Int,
) : PaymentDetailResponse()
}
fun PaymentDetailEntity.toPaymentDetailResponse(): PaymentDetailResponse {
return when (this) {
is PaymentCardDetailEntity -> this.toCardDetailResponse()
is PaymentBankTransferDetailEntity -> this.toBankTransferDetailResponse()
is PaymentEasypayPrepaidDetailEntity -> this.toEasyPayPrepaidDetailResponse()
else -> throw PaymentException(PaymentErrorCode.NOT_SUPPORTED_PAYMENT_TYPE)
}
}
fun PaymentCardDetailEntity.toCardDetailResponse(): CardDetailResponse {
return CardDetailResponse(
issuerCode = this.issuerCode.koreanName,
cardType = this.cardType.koreanName,
ownerType = this.ownerType.koreanName,
cardNumber = this.cardNumber,
amount = this.amount,
approvalNumber = this.approvalNumber,
installmentPlanMonths = this.installmentPlanMonths,
easypayProviderName = this.easypayProviderCode?.koreanName,
easypayDiscountAmount = this.easypayDiscountAmount
)
}
fun PaymentBankTransferDetailEntity.toBankTransferDetailResponse(): BankTransferDetailResponse {
return BankTransferDetailResponse(
bankName = this.bankCode.koreanName
)
}
fun PaymentEasypayPrepaidDetailEntity.toEasyPayPrepaidDetailResponse(): EasyPayPrepaidDetailResponse {
return EasyPayPrepaidDetailResponse(
providerName = this.easypayProviderCode.koreanName,
amount = this.amount,
discountAmount = this.discountAmount
)
}
data class PaymentCancelDetailResponse(
val cancellationRequestedAt: LocalDateTime,
val cancellationApprovedAt: OffsetDateTime?,
val cancelReason: String,
val canceledBy: Long,
)
fun CanceledPaymentEntityV2.toCancelDetailResponse(): PaymentCancelDetailResponse {
return PaymentCancelDetailResponse(
cancellationRequestedAt = this.requestedAt,
cancellationApprovedAt = this.canceledAt,
cancelReason = this.cancelReason,
canceledBy = this.canceledBy
)
}