feat(gift): 선물 조회 서비스를 추가한다
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
package kr.co.vividnext.sodalive.v2.gift.application
|
||||
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.Gift
|
||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftCategoryRepository
|
||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftDelivery
|
||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftDeliveryRepository
|
||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftRepository
|
||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftSizePriceRepository
|
||||
import kr.co.vividnext.sodalive.v2.gift.domain.GiftDirection
|
||||
import kr.co.vividnext.sodalive.v2.gift.domain.GiftListType
|
||||
import kr.co.vividnext.sodalive.v2.gift.domain.GiftStatus
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.data.domain.Sort
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.time.LocalDateTime
|
||||
|
||||
data class GiftFormOptionsResult(
|
||||
val sizes: List<GiftSizePriceResult>,
|
||||
val categories: List<GiftCategoryResult>
|
||||
)
|
||||
|
||||
data class GiftListResult(
|
||||
val totalCount: Long,
|
||||
val items: List<GiftListItemResult>,
|
||||
val page: Int,
|
||||
val size: Int,
|
||||
val hasNext: Boolean
|
||||
)
|
||||
|
||||
data class GiftListItemResult(
|
||||
val applicationNo: String,
|
||||
val direction: GiftDirection,
|
||||
val status: GiftStatus,
|
||||
val statusName: String,
|
||||
val priceCan: Int,
|
||||
val categoryName: String,
|
||||
val sizeName: String,
|
||||
val createdAt: LocalDateTime?
|
||||
)
|
||||
|
||||
data class GiftDetailResult(
|
||||
val applicationNo: String,
|
||||
val direction: GiftDirection,
|
||||
val status: GiftStatus,
|
||||
val statusName: String,
|
||||
val giftInfo: GiftDetailInfoResult,
|
||||
val senderInfo: GiftAddressResult?,
|
||||
val recipientAddress: GiftAddressResult?,
|
||||
val mailbox: GiftMailboxResult?,
|
||||
val trackingRequired: Boolean,
|
||||
val recipientAddressRequired: Boolean,
|
||||
val recipientAddressDeadlineAt: LocalDateTime?,
|
||||
val delivery: GiftDeliveryInfoResult,
|
||||
val statusTimeline: List<GiftStatusTimelineResult>
|
||||
)
|
||||
|
||||
data class GiftDetailInfoResult(
|
||||
val recipientCreatorNickname: String?,
|
||||
val senderNickname: String?,
|
||||
val sizeName: String,
|
||||
val categoryName: String,
|
||||
val applicationNo: String?,
|
||||
val paidCan: Int?,
|
||||
val tracking: String?,
|
||||
val shippingRequestedAt: LocalDateTime?
|
||||
)
|
||||
|
||||
data class GiftAddressResult(
|
||||
val name: String,
|
||||
val phoneNumber: String,
|
||||
val address: String
|
||||
)
|
||||
|
||||
data class GiftMailboxResult(
|
||||
val name: String,
|
||||
val address: String,
|
||||
val phoneNumber: String
|
||||
)
|
||||
|
||||
data class GiftDeliveryInfoResult(
|
||||
val canceledAt: LocalDateTime?,
|
||||
val undeliverableAt: LocalDateTime?,
|
||||
val undeliverableReason: String?
|
||||
)
|
||||
|
||||
data class GiftStatusTimelineResult(
|
||||
val status: GiftStatus,
|
||||
val statusName: String,
|
||||
val occurredAt: LocalDateTime?
|
||||
)
|
||||
|
||||
@Service
|
||||
class GiftQueryService(
|
||||
private val categoryRepository: GiftCategoryRepository,
|
||||
private val sizePriceRepository: GiftSizePriceRepository,
|
||||
private val giftRepository: GiftRepository,
|
||||
private val deliveryRepository: GiftDeliveryRepository,
|
||||
private val memberRepository: MemberRepository
|
||||
) {
|
||||
@Transactional(readOnly = true)
|
||||
fun getFormOptions() = GiftFormOptionsResult(
|
||||
sizes = sizePriceRepository.findAll().filter { it.isActive }.map {
|
||||
GiftSizePriceResult(it.sizeCode, it.sizeCode.displayName, it.basePriceCan, it.salePriceCan, it.isActive)
|
||||
},
|
||||
categories = categoryRepository.findAll().filter { it.isActive }.map {
|
||||
GiftCategoryResult(it.id!!, it.categoryCode, it.name, it.requiresDamageWaiver, it.isActive)
|
||||
}
|
||||
)
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun getGifts(memberId: Long, type: GiftListType, page: Int, size: Int): GiftListResult {
|
||||
val normalizedPage = page.coerceAtLeast(0)
|
||||
val normalizedSize = if (size <= 0) DEFAULT_PAGE_SIZE else size
|
||||
val pageable = PageRequest.of(normalizedPage, normalizedSize, Sort.by(Sort.Direction.DESC, "createdAt", "id"))
|
||||
val gifts = when (type) {
|
||||
GiftListType.SENT -> giftRepository.findBySenderMemberId(memberId, pageable)
|
||||
GiftListType.RECEIVED -> giftRepository.findByRecipientMemberIdAndStatusIn(
|
||||
memberId,
|
||||
RECIPIENT_VISIBLE_STATUSES,
|
||||
pageable
|
||||
)
|
||||
GiftListType.ALL -> giftRepository.findBySenderMemberIdOrRecipientMemberIdAndStatusIn(
|
||||
memberId,
|
||||
memberId,
|
||||
RECIPIENT_VISIBLE_STATUSES,
|
||||
pageable
|
||||
)
|
||||
}
|
||||
|
||||
return GiftListResult(
|
||||
totalCount = gifts.totalElements,
|
||||
items = gifts.content.map { it.toGiftListItem(memberId) },
|
||||
page = normalizedPage,
|
||||
size = normalizedSize,
|
||||
hasNext = gifts.hasNext()
|
||||
)
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun getGiftDetail(memberId: Long, applicationNo: String): GiftDetailResult {
|
||||
val gift = giftRepository.findByApplicationNo(applicationNo) ?: throw invalidRequest()
|
||||
val delivery = deliveryRepository.findByGiftId(gift.id ?: throw invalidRequest()) ?: throw invalidRequest()
|
||||
val direction = gift.directionFor(memberId)
|
||||
val sender = memberRepository.findByIdOrNull(gift.senderMemberId) ?: throw invalidRequest()
|
||||
val recipient = memberRepository.findByIdOrNull(gift.recipientMemberId) ?: throw invalidRequest()
|
||||
val recipientAddress = if (direction == GiftDirection.RECEIVED) delivery.toRecipientAddressResult() else null
|
||||
|
||||
return GiftDetailResult(
|
||||
applicationNo = gift.applicationNo,
|
||||
direction = direction,
|
||||
status = gift.status,
|
||||
statusName = gift.status.displayName,
|
||||
giftInfo = GiftDetailInfoResult(
|
||||
recipientCreatorNickname = recipient.nickname.takeIf { direction == GiftDirection.SENT },
|
||||
senderNickname = sender.nickname.takeIf { direction == GiftDirection.RECEIVED },
|
||||
sizeName = gift.sizeCode.displayName,
|
||||
categoryName = gift.categoryNameSnapshot,
|
||||
applicationNo = gift.applicationNo.takeIf { direction == GiftDirection.SENT },
|
||||
paidCan = gift.salePriceCan.takeIf { direction == GiftDirection.SENT },
|
||||
tracking = delivery.tracking().takeIf { direction == GiftDirection.SENT },
|
||||
shippingRequestedAt = gift.createdAt.takeIf { direction == GiftDirection.RECEIVED }
|
||||
),
|
||||
senderInfo = delivery.toSenderInfo().takeIf { direction == GiftDirection.SENT },
|
||||
recipientAddress = recipientAddress,
|
||||
mailbox = null,
|
||||
trackingRequired = direction == GiftDirection.SENT && gift.status == GiftStatus.RECEIVED,
|
||||
recipientAddressRequired = direction == GiftDirection.RECEIVED && recipientAddress == null &&
|
||||
gift.status !in TERMINAL_STATUSES,
|
||||
recipientAddressDeadlineAt = delivery.recipientAddressDeadlineAt,
|
||||
delivery = GiftDeliveryInfoResult(
|
||||
canceledAt = delivery.canceledAt,
|
||||
undeliverableAt = delivery.undeliverableAt,
|
||||
undeliverableReason = delivery.undeliverableReason
|
||||
),
|
||||
statusTimeline = gift.toStatusTimeline(delivery)
|
||||
)
|
||||
}
|
||||
|
||||
private fun Gift.toGiftListItem(memberId: Long) = GiftListItemResult(
|
||||
applicationNo = applicationNo,
|
||||
direction = if (senderMemberId == memberId) GiftDirection.SENT else GiftDirection.RECEIVED,
|
||||
status = status,
|
||||
statusName = status.displayName,
|
||||
priceCan = salePriceCan,
|
||||
categoryName = categoryNameSnapshot,
|
||||
sizeName = sizeCode.displayName,
|
||||
createdAt = createdAt
|
||||
)
|
||||
|
||||
private fun Gift.directionFor(memberId: Long) = when {
|
||||
senderMemberId == memberId -> GiftDirection.SENT
|
||||
recipientMemberId == memberId && status in RECIPIENT_VISIBLE_STATUSES -> GiftDirection.RECEIVED
|
||||
else -> throw invalidRequest()
|
||||
}
|
||||
|
||||
private fun GiftDelivery.toSenderInfo() = GiftAddressResult(
|
||||
name = senderName,
|
||||
phoneNumber = senderPhoneNumber,
|
||||
address = formatAddress(senderZipCode, senderAddress, senderAddressDetail)
|
||||
)
|
||||
|
||||
private fun GiftDelivery.toRecipientAddressResult(): GiftAddressResult? {
|
||||
val name = recipientName ?: return null
|
||||
val phoneNumber = recipientPhoneNumber ?: return null
|
||||
val zipCode = recipientZipCode ?: return null
|
||||
val address = recipientAddress ?: return null
|
||||
|
||||
return GiftAddressResult(name, phoneNumber, formatAddress(zipCode, address, recipientAddressDetail))
|
||||
}
|
||||
|
||||
private fun GiftDelivery.tracking() = if (courierCompanyName != null && trackingNumber != null) {
|
||||
"${courierCompanyName}_$trackingNumber"
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
private fun Gift.toStatusTimeline(delivery: GiftDelivery) = NORMAL_PROGRESS_STATUSES.map { timelineStatus ->
|
||||
GiftStatusTimelineResult(
|
||||
status = timelineStatus,
|
||||
statusName = timelineStatus.displayName,
|
||||
occurredAt = when (timelineStatus) {
|
||||
GiftStatus.RECEIVED -> createdAt
|
||||
GiftStatus.TRACKING_REGISTERED -> delivery.trackingRegisteredAt
|
||||
GiftStatus.ARRIVED_AT_MAILBOX -> delivery.arrivedAtMailboxAt
|
||||
GiftStatus.INSPECTION_COMPLETED -> delivery.inspectionCompletedAt
|
||||
GiftStatus.DELIVERED -> delivery.deliveredAt
|
||||
GiftStatus.UNDELIVERABLE, GiftStatus.CANCELED -> null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatAddress(zipCode: String, address: String, detail: String?) =
|
||||
"($zipCode) $address" + if (detail.isNullOrBlank()) "" else ", $detail"
|
||||
|
||||
private fun invalidRequest() = SodaException(messageKey = "common.error.invalid_request")
|
||||
|
||||
private companion object {
|
||||
const val DEFAULT_PAGE_SIZE = 20
|
||||
val RECIPIENT_VISIBLE_STATUSES = setOf(
|
||||
GiftStatus.TRACKING_REGISTERED,
|
||||
GiftStatus.ARRIVED_AT_MAILBOX,
|
||||
GiftStatus.INSPECTION_COMPLETED,
|
||||
GiftStatus.DELIVERED,
|
||||
GiftStatus.UNDELIVERABLE
|
||||
)
|
||||
val NORMAL_PROGRESS_STATUSES = listOf(
|
||||
GiftStatus.RECEIVED,
|
||||
GiftStatus.TRACKING_REGISTERED,
|
||||
GiftStatus.ARRIVED_AT_MAILBOX,
|
||||
GiftStatus.INSPECTION_COMPLETED,
|
||||
GiftStatus.DELIVERED
|
||||
)
|
||||
val TERMINAL_STATUSES = setOf(GiftStatus.DELIVERED, GiftStatus.UNDELIVERABLE, GiftStatus.CANCELED)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user