feat(gift): 선물 신청 명령 서비스를 추가한다
This commit is contained in:
@@ -0,0 +1,368 @@
|
||||
package kr.co.vividnext.sodalive.v2.gift.application
|
||||
|
||||
import kr.co.vividnext.sodalive.can.payment.CanPaymentService
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
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.GiftReview
|
||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftReviewRepository
|
||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftSizePriceRepository
|
||||
import kr.co.vividnext.sodalive.v2.gift.domain.GiftSize
|
||||
import kr.co.vividnext.sodalive.v2.gift.domain.GiftStatus
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.time.LocalDateTime
|
||||
|
||||
data class GiftApplicationCommand(
|
||||
val recipientMemberId: Long,
|
||||
val senderName: String,
|
||||
val senderPhoneNumber: String,
|
||||
val senderZipCode: String,
|
||||
val senderAddress: String,
|
||||
val senderAddressDetail: String?,
|
||||
val sizeCode: GiftSize,
|
||||
val categoryId: Long,
|
||||
val senderTermsAgreed: Boolean,
|
||||
val senderPrivacyAgreed: Boolean,
|
||||
val damageWaiverAgreed: Boolean
|
||||
)
|
||||
|
||||
data class GiftApplicationResult(
|
||||
val applicationNo: String,
|
||||
val status: GiftStatus,
|
||||
val statusName: String,
|
||||
val priceCan: Int,
|
||||
val trackingDeadlineAt: LocalDateTime
|
||||
)
|
||||
|
||||
data class GiftCancellationResult(
|
||||
val applicationNo: String,
|
||||
val status: GiftStatus,
|
||||
val statusName: String,
|
||||
val priceCan: Int,
|
||||
val canceledAt: LocalDateTime
|
||||
)
|
||||
|
||||
data class GiftTrackingRegistrationCommand(
|
||||
val courierCompanyName: String,
|
||||
val trackingNumber: String
|
||||
)
|
||||
|
||||
data class GiftTrackingRegistrationResult(
|
||||
val applicationNo: String,
|
||||
val status: GiftStatus,
|
||||
val statusName: String,
|
||||
val courierCompanyName: String,
|
||||
val trackingNumber: String,
|
||||
val trackingRegisteredAt: LocalDateTime,
|
||||
val recipientAddressDeadlineAt: LocalDateTime
|
||||
)
|
||||
|
||||
data class GiftRecipientAddressRegistrationCommand(
|
||||
val name: String,
|
||||
val phoneNumber: String,
|
||||
val zipCode: String,
|
||||
val address: String,
|
||||
val addressDetail: String?,
|
||||
val recipientTermsAgreed: Boolean,
|
||||
val recipientPrivacyAgreed: Boolean
|
||||
)
|
||||
|
||||
data class GiftRecipientAddressRegistrationResult(
|
||||
val applicationNo: String,
|
||||
val status: GiftStatus,
|
||||
val statusName: String,
|
||||
val recipientAddressRegisteredAt: LocalDateTime
|
||||
)
|
||||
|
||||
data class GiftDeliveryConfirmationResult(
|
||||
val applicationNo: String,
|
||||
val status: GiftStatus,
|
||||
val statusName: String,
|
||||
val deliveredAt: LocalDateTime
|
||||
)
|
||||
|
||||
data class GiftReviewCommand(
|
||||
val rating: Int,
|
||||
val keywords: List<String>,
|
||||
val comment: String?
|
||||
)
|
||||
|
||||
data class GiftReviewResult(
|
||||
val reviewId: Long,
|
||||
val applicationNo: String,
|
||||
val rating: Int,
|
||||
val keywords: List<String>,
|
||||
val comment: String?,
|
||||
val createdAt: LocalDateTime?
|
||||
)
|
||||
|
||||
@Service
|
||||
class GiftCommandService(
|
||||
private val giftRepository: GiftRepository,
|
||||
private val deliveryRepository: GiftDeliveryRepository,
|
||||
private val reviewRepository: GiftReviewRepository,
|
||||
private val categoryRepository: GiftCategoryRepository,
|
||||
private val sizePriceRepository: GiftSizePriceRepository,
|
||||
private val memberRepository: MemberRepository,
|
||||
private val applicationNoGenerator: GiftApplicationNoGenerator,
|
||||
private val canPaymentService: CanPaymentService,
|
||||
private val giftPushService: GiftPushService,
|
||||
private val now: () -> LocalDateTime = { LocalDateTime.now() }
|
||||
) {
|
||||
@Transactional
|
||||
fun registerGift(member: Member, command: GiftApplicationCommand): GiftApplicationResult {
|
||||
validate(command)
|
||||
val senderMemberId = member.id ?: throw invalidRequest()
|
||||
memberRepository.findCreatorByIdOrNull(command.recipientMemberId) ?: throw invalidRequest()
|
||||
val category = categoryRepository.findByIdOrNull(command.categoryId)?.takeIf { it.isActive } ?: throw invalidRequest()
|
||||
if (category.requiresDamageWaiver && !command.damageWaiverAgreed) throw invalidRequest()
|
||||
val sizePrice = sizePriceRepository.findBySizeCode(command.sizeCode)?.takeIf { it.isActive } ?: throw invalidRequest()
|
||||
val appliedAt = now()
|
||||
val trackingDeadlineAt = appliedAt.plusDays(3)
|
||||
val applicationNo = applicationNoGenerator.generate(appliedAt)
|
||||
val canUsageId = canPaymentService.spendGiftCan(
|
||||
senderMemberId,
|
||||
sizePrice.salePriceCan,
|
||||
member.container.ifBlank { "web" }
|
||||
)
|
||||
val gift = giftRepository.save(
|
||||
Gift(
|
||||
applicationNo = applicationNo,
|
||||
senderMemberId = senderMemberId,
|
||||
recipientMemberId = command.recipientMemberId,
|
||||
status = GiftStatus.RECEIVED,
|
||||
sizeCode = command.sizeCode,
|
||||
categoryId = category.id ?: throw invalidRequest(),
|
||||
categoryNameSnapshot = category.name,
|
||||
salePriceCan = sizePrice.salePriceCan,
|
||||
canUsageId = canUsageId,
|
||||
senderTermsAgreed = command.senderTermsAgreed,
|
||||
senderPrivacyAgreed = command.senderPrivacyAgreed,
|
||||
senderTermsAgreedAt = appliedAt,
|
||||
senderPrivacyAgreedAt = appliedAt,
|
||||
damageWaiverAgreed = command.damageWaiverAgreed
|
||||
)
|
||||
)
|
||||
deliveryRepository.save(
|
||||
GiftDelivery(
|
||||
giftId = gift.id ?: throw invalidRequest(),
|
||||
senderName = command.senderName,
|
||||
senderPhoneNumber = command.senderPhoneNumber,
|
||||
senderZipCode = command.senderZipCode,
|
||||
senderAddress = command.senderAddress,
|
||||
senderAddressDetail = command.senderAddressDetail,
|
||||
trackingDeadlineAt = trackingDeadlineAt
|
||||
)
|
||||
)
|
||||
return GiftApplicationResult(
|
||||
applicationNo = applicationNo,
|
||||
status = GiftStatus.RECEIVED,
|
||||
statusName = GiftStatus.RECEIVED.displayName,
|
||||
priceCan = sizePrice.salePriceCan,
|
||||
trackingDeadlineAt = trackingDeadlineAt
|
||||
)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun writeReview(member: Member, applicationNo: String, command: GiftReviewCommand): GiftReviewResult {
|
||||
val senderMemberId = member.id ?: throw invalidRequest()
|
||||
val gift = giftRepository.findByApplicationNo(applicationNo) ?: throw invalidRequest()
|
||||
val giftId = gift.id ?: throw invalidRequest()
|
||||
if (
|
||||
gift.senderMemberId != senderMemberId ||
|
||||
gift.status != GiftStatus.DELIVERED ||
|
||||
reviewRepository.findByGiftId(giftId) != null
|
||||
) {
|
||||
throw invalidRequest()
|
||||
}
|
||||
validate(command)
|
||||
|
||||
val review = reviewRepository.save(
|
||||
GiftReview(
|
||||
giftId = giftId,
|
||||
senderMemberId = senderMemberId,
|
||||
rating = command.rating,
|
||||
keywords = command.keywords.joinToString(","),
|
||||
comment = command.comment
|
||||
)
|
||||
)
|
||||
|
||||
return GiftReviewResult(
|
||||
reviewId = review.id ?: throw invalidRequest(),
|
||||
applicationNo = gift.applicationNo,
|
||||
rating = review.rating,
|
||||
keywords = review.keywords?.takeIf { it.isNotBlank() }?.split(",") ?: emptyList(),
|
||||
comment = review.comment,
|
||||
createdAt = review.createdAt
|
||||
)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun cancelGift(member: Member, applicationNo: String): GiftCancellationResult {
|
||||
val senderMemberId = member.id ?: throw invalidRequest()
|
||||
val gift = giftRepository.findByApplicationNo(applicationNo) ?: throw invalidRequest()
|
||||
if (gift.senderMemberId != senderMemberId || gift.status != GiftStatus.RECEIVED) throw invalidRequest()
|
||||
|
||||
val canceledAt = now()
|
||||
gift.status = GiftStatus.CANCELED
|
||||
deliveryRepository.findByGiftId(gift.id ?: throw invalidRequest())?.canceledAt = canceledAt
|
||||
canPaymentService.refundGiftCan(senderMemberId, gift.canUsageId)
|
||||
|
||||
return GiftCancellationResult(
|
||||
applicationNo = gift.applicationNo,
|
||||
status = gift.status,
|
||||
statusName = gift.status.displayName,
|
||||
priceCan = gift.salePriceCan,
|
||||
canceledAt = canceledAt
|
||||
)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun registerTracking(
|
||||
member: Member,
|
||||
applicationNo: String,
|
||||
command: GiftTrackingRegistrationCommand
|
||||
): GiftTrackingRegistrationResult {
|
||||
val senderMemberId = member.id ?: throw invalidRequest()
|
||||
val gift = giftRepository.findByApplicationNo(applicationNo) ?: throw invalidRequest()
|
||||
if (gift.senderMemberId != senderMemberId || gift.status != GiftStatus.RECEIVED) throw invalidRequest()
|
||||
validate(command)
|
||||
val delivery = deliveryRepository.findByGiftId(gift.id ?: throw invalidRequest()) ?: throw invalidRequest()
|
||||
val trackingRegisteredAt = now()
|
||||
val recipientAddressDeadlineAt = trackingRegisteredAt.plusDays(7)
|
||||
|
||||
gift.status = GiftStatus.TRACKING_REGISTERED
|
||||
delivery.courierCompanyName = command.courierCompanyName
|
||||
delivery.trackingNumber = command.trackingNumber
|
||||
delivery.trackingRegisteredAt = trackingRegisteredAt
|
||||
delivery.recipientAddressDeadlineAt = recipientAddressDeadlineAt
|
||||
giftPushService.sendTrackingRegisteredToRecipient(gift.applicationNo, gift.recipientMemberId)
|
||||
|
||||
return GiftTrackingRegistrationResult(
|
||||
applicationNo = gift.applicationNo,
|
||||
status = gift.status,
|
||||
statusName = gift.status.displayName,
|
||||
courierCompanyName = command.courierCompanyName,
|
||||
trackingNumber = command.trackingNumber,
|
||||
trackingRegisteredAt = trackingRegisteredAt,
|
||||
recipientAddressDeadlineAt = recipientAddressDeadlineAt
|
||||
)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun registerRecipientAddress(
|
||||
member: Member,
|
||||
applicationNo: String,
|
||||
command: GiftRecipientAddressRegistrationCommand
|
||||
): GiftRecipientAddressRegistrationResult {
|
||||
val recipientMemberId = member.id ?: throw invalidRequest()
|
||||
val gift = giftRepository.findByApplicationNo(applicationNo) ?: throw invalidRequest()
|
||||
if (gift.recipientMemberId != recipientMemberId || gift.status !in RECIPIENT_ADDRESS_REGISTRABLE_STATUSES) {
|
||||
throw invalidRequest()
|
||||
}
|
||||
validate(command)
|
||||
val delivery = deliveryRepository.findByGiftId(gift.id ?: throw invalidRequest()) ?: throw invalidRequest()
|
||||
val registeredAt = now()
|
||||
|
||||
delivery.recipientName = command.name
|
||||
delivery.recipientPhoneNumber = command.phoneNumber
|
||||
delivery.recipientZipCode = command.zipCode
|
||||
delivery.recipientAddress = command.address
|
||||
delivery.recipientAddressDetail = command.addressDetail
|
||||
gift.recipientTermsAgreed = command.recipientTermsAgreed
|
||||
gift.recipientPrivacyAgreed = command.recipientPrivacyAgreed
|
||||
gift.recipientTermsAgreedAt = registeredAt
|
||||
gift.recipientPrivacyAgreedAt = registeredAt
|
||||
|
||||
return GiftRecipientAddressRegistrationResult(
|
||||
applicationNo = gift.applicationNo,
|
||||
status = gift.status,
|
||||
statusName = gift.status.displayName,
|
||||
recipientAddressRegisteredAt = registeredAt
|
||||
)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun confirmDelivery(member: Member, applicationNo: String): GiftDeliveryConfirmationResult {
|
||||
val recipientMemberId = member.id ?: throw invalidRequest()
|
||||
val gift = giftRepository.findByApplicationNo(applicationNo) ?: throw invalidRequest()
|
||||
if (gift.recipientMemberId != recipientMemberId || gift.status != GiftStatus.INSPECTION_COMPLETED) {
|
||||
throw invalidRequest()
|
||||
}
|
||||
val delivery = deliveryRepository.findByGiftId(gift.id ?: throw invalidRequest()) ?: throw invalidRequest()
|
||||
val deliveredAt = now()
|
||||
|
||||
gift.status = GiftStatus.DELIVERED
|
||||
delivery.deliveredAt = deliveredAt
|
||||
|
||||
return GiftDeliveryConfirmationResult(
|
||||
applicationNo = gift.applicationNo,
|
||||
status = gift.status,
|
||||
statusName = gift.status.displayName,
|
||||
deliveredAt = deliveredAt
|
||||
)
|
||||
}
|
||||
|
||||
private fun validate(command: GiftApplicationCommand) {
|
||||
if (
|
||||
command.senderName.isBlank() ||
|
||||
command.senderPhoneNumber.isBlank() ||
|
||||
command.senderZipCode.isBlank() ||
|
||||
command.senderAddress.isBlank() ||
|
||||
!command.senderTermsAgreed ||
|
||||
!command.senderPrivacyAgreed
|
||||
) {
|
||||
throw invalidRequest()
|
||||
}
|
||||
}
|
||||
|
||||
private fun validate(command: GiftTrackingRegistrationCommand) {
|
||||
if (
|
||||
command.courierCompanyName.isBlank() ||
|
||||
command.courierCompanyName.length > 50 ||
|
||||
command.trackingNumber.isBlank()
|
||||
) {
|
||||
throw invalidRequest()
|
||||
}
|
||||
}
|
||||
|
||||
private fun validate(command: GiftRecipientAddressRegistrationCommand) {
|
||||
if (
|
||||
command.name.isBlank() ||
|
||||
command.phoneNumber.isBlank() ||
|
||||
command.zipCode.isBlank() ||
|
||||
command.address.isBlank() ||
|
||||
!command.recipientTermsAgreed ||
|
||||
!command.recipientPrivacyAgreed
|
||||
) {
|
||||
throw invalidRequest()
|
||||
}
|
||||
}
|
||||
|
||||
private fun validate(command: GiftReviewCommand) {
|
||||
if (
|
||||
command.rating !in 1..5 ||
|
||||
command.keywords.any { it.length > 255 } ||
|
||||
(command.comment?.length ?: 0) > 255
|
||||
) {
|
||||
throw invalidRequest()
|
||||
}
|
||||
}
|
||||
|
||||
private fun invalidRequest() = SodaException(messageKey = "common.error.invalid_request")
|
||||
|
||||
private companion object {
|
||||
val RECIPIENT_ADDRESS_REGISTRABLE_STATUSES = setOf(
|
||||
GiftStatus.TRACKING_REGISTERED,
|
||||
GiftStatus.ARRIVED_AT_MAILBOX,
|
||||
GiftStatus.INSPECTION_COMPLETED
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user