Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

선물 받은 쿠폰 코드 등록 유효기간 정책을 설정한다. #23

Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.example.estdelivery.coupon.application

import com.example.estdelivery.coupon.application.port.`in`.FindAvailableGiftCouponUseCase
import com.example.estdelivery.coupon.application.port.`in`.web.dto.GiftCouponResponse
import com.example.estdelivery.coupon.application.port.`in`.web.dto.GiftCouponResponses
import com.example.estdelivery.coupon.application.port.out.LoadMemberStatePort
import com.example.estdelivery.coupon.domain.coupon.Coupon
import com.example.estdelivery.coupon.domain.coupon.GiftCoupon
import com.example.estdelivery.coupon.domain.coupon.Coupon.FixDiscountCoupon
import com.example.estdelivery.coupon.domain.coupon.Coupon.RateDiscountCoupon
import com.example.estdelivery.coupon.domain.member.Member

class FindAvailableGiftCouponService(
Expand All @@ -16,12 +19,22 @@ class FindAvailableGiftCouponService(
* 3. 선물 가능한 쿠폰을 고른다.
* 4. 선물 가능한 쿠폰을 반환한다.
*/
override fun findAvailableGiftCoupon(memberId: Long): List<GiftCoupon> {
override fun findAvailableGiftCoupon(memberId: Long): GiftCouponResponses {
val member = findMember(memberId)
val myCouponBook = member.showMyCouponBook()
return myCouponBook
.filter { isGiftAvailable(it) }
.map { GiftCoupon(it) }
.map {
GiftCouponResponse(
id = it.id!!,
name = it.name,
discountAmount = if (it is FixDiscountCoupon) it.discountAmount
else (it as RateDiscountCoupon).discountRate,
discountType = it.couponType
)
}.let {
GiftCouponResponses(it)
}
}

private fun isGiftAvailable(coupon: Coupon): Boolean {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.example.estdelivery.coupon.application

import com.example.estdelivery.coupon.application.port.`in`.GiftCouponByMessageUseCase
import com.example.estdelivery.coupon.application.port.`in`.web.dto.GiftMessageResponse
import com.example.estdelivery.coupon.application.port.out.CreateGiftCouponMessageStatePort
import com.example.estdelivery.coupon.application.port.out.LoadMemberStatePort
import com.example.estdelivery.coupon.application.port.out.UpdateMemberStatePort
Expand All @@ -10,6 +11,7 @@ import com.example.estdelivery.coupon.domain.coupon.Coupon
import com.example.estdelivery.coupon.domain.coupon.GiftCouponCode
import com.example.estdelivery.coupon.domain.coupon.GiftMessage
import com.example.estdelivery.coupon.domain.member.Member
import java.net.URL

class GiftCouponByMessageService(
loadMemberStatePort: LoadMemberStatePort,
Expand Down Expand Up @@ -37,7 +39,7 @@ class GiftCouponByMessageService(
* @param couponId 선물할 쿠폰 식별자
* @param giftMessage 선물 메시지
*/
override fun sendGiftAvailableCoupon(memberId: Long, couponId: Long, giftMessage: String): GiftMessage {
override fun sendGiftAvailableCoupon(memberId: Long, couponId: Long, giftMessage: String): GiftMessageResponse {
return transactionArea.run {
val sender = findMember(memberId)
val coupon = sender.showMyCouponBook().find { it.id == couponId }
Expand All @@ -47,7 +49,14 @@ class GiftCouponByMessageService(
val couponCode: GiftCouponCode = getGiftCouponCode()
sender.useCoupon(coupon)
updateMembersCoupon(sender)
createGiftMessage(sender, coupon, giftMessage, couponCode)
createGiftMessage(sender, coupon, giftMessage, couponCode).let {
GiftMessageResponse(
senderName = it.sender.name,
description = it.giftMessage,
enrollEndDate = it.giftCoupon.enrollEndDate,
enrollHref = URL("http", "localhost", 8080, "/gift-coupons/enroll/${it.giftCouponCode.code}")
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.example.estdelivery.coupon.application.port.`in`

import com.example.estdelivery.coupon.domain.coupon.GiftCoupon
import com.example.estdelivery.coupon.application.port.`in`.web.dto.GiftCouponResponses

interface FindAvailableGiftCouponUseCase {
/**
* 회원은 가게에 발행된 쿠폰 중 선물 가능한 쿠폰을 조회한다.
* @param memberId
*/
fun findAvailableGiftCoupon(memberId: Long): List<GiftCoupon>
fun findAvailableGiftCoupon(memberId: Long): GiftCouponResponses
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.example.estdelivery.coupon.application.port.`in`

import com.example.estdelivery.coupon.domain.coupon.GiftMessage
import com.example.estdelivery.coupon.application.port.`in`.web.dto.GiftMessageResponse

interface GiftCouponByMessageUseCase {
fun sendGiftAvailableCoupon(memberId: Long, couponId: Long, giftMessage: String): GiftMessage
fun sendGiftAvailableCoupon(memberId: Long, couponId: Long, giftMessage: String): GiftMessageResponse
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@ package com.example.estdelivery.coupon.application.port.`in`.web
import com.example.estdelivery.coupon.application.port.`in`.EnrollCouponByMessageUseCase
import com.example.estdelivery.coupon.application.port.`in`.FindAvailableGiftCouponUseCase
import com.example.estdelivery.coupon.application.port.`in`.GiftCouponByMessageUseCase
import com.example.estdelivery.coupon.application.port.`in`.web.dto.GiftCouponResponse
import com.example.estdelivery.coupon.application.port.`in`.web.dto.GiftCouponResponses
import com.example.estdelivery.coupon.application.port.`in`.web.dto.GiftMessageResponse
import com.example.estdelivery.coupon.domain.coupon.Coupon.FixDiscountCoupon
import com.example.estdelivery.coupon.domain.coupon.Coupon.RateDiscountCoupon
import com.example.estdelivery.coupon.domain.coupon.GiftCouponCode
import java.net.URL
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
Expand All @@ -30,35 +26,15 @@ class GiftCouponMessageController(
@GetMapping
fun findAvailableGiftCoupons(@RequestHeader(value = MEMBER_ID) memberId: Long): GiftCouponResponses =
findAvailableGiftCouponUseCase.findAvailableGiftCoupon(memberId)
.map {
GiftCouponResponse(
id = it.coupon.id!!,
name = it.coupon.name,
discountAmount = if (it.coupon is FixDiscountCoupon) it.coupon.discountAmount
else (it.coupon as RateDiscountCoupon).discountRate,
discountType = it.coupon.couponType
)
}.let {
GiftCouponResponses(it)
}


@PostMapping("/send/{couponId}")
fun sendGiftAvailableCoupon(
@RequestHeader(value = MEMBER_ID) memberId: Long,
@RequestParam message: String,
@PathVariable couponId: Long,
): GiftMessageResponse =
giftCouponByMessageUseCase.sendGiftAvailableCoupon(
memberId,
couponId,
message,
).let {
GiftMessageResponse(
senderName = it.sender.name,
description = it.giftMessage,
enrollHref = URL("http", "localhost", 8080, "/gift-coupons/enroll/${it.giftCouponCode.code}")
)
}
giftCouponByMessageUseCase.sendGiftAvailableCoupon(memberId, couponId, message)

@GetMapping("/enroll/{code}")
fun enrollGiftCoupon(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.example.estdelivery.coupon.application.port.`in`.web.dto

import java.net.URL
import java.time.LocalDate

data class GiftMessageResponse(
val senderName: String,
val description: String,
val enrollEndDate: LocalDate,
val enrollHref: URL,
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,44 @@ import com.example.estdelivery.coupon.application.port.out.ValidateGiftCouponCod
import com.example.estdelivery.coupon.application.port.out.adapter.persistence.entity.GiftMessageEntity
import com.example.estdelivery.coupon.application.port.out.adapter.persistence.mapper.fromCoupon
import com.example.estdelivery.coupon.application.port.out.adapter.persistence.mapper.toCoupon
import com.example.estdelivery.coupon.application.port.out.adapter.persistence.repository.EnrollDateRepository
import com.example.estdelivery.coupon.application.port.out.adapter.persistence.repository.GiftMessageRepository
import com.example.estdelivery.coupon.domain.coupon.Coupon
import com.example.estdelivery.coupon.domain.coupon.GiftCoupon
import com.example.estdelivery.coupon.domain.coupon.GiftCouponCode
import com.example.estdelivery.coupon.domain.coupon.GiftMessage
import com.example.estdelivery.coupon.domain.member.Member
import jakarta.transaction.Transactional
import java.time.LocalDate
import org.springframework.stereotype.Component

@Component
class GiftCouponMessageAdapter(
private val giftMessageRepository: GiftMessageRepository,
private val enrollDateRepository: EnrollDateRepository,
) : CreateGiftCouponMessageStatePort, ValidateGiftCouponCodeStatePort, UseGiftCouponCodeStatePort,
LoadGiftCouponStatePort {
override fun create(sender: Member, coupon: Coupon, message: String, giftCouponCode: GiftCouponCode): GiftMessage {
val enrollTerm = enrollDateRepository.findLatestPolicy()
?: throw IllegalStateException("EnrollTerm not found")

val enrollDate = LocalDate.now()

val giftMessageEntity = giftMessageRepository.save(
GiftMessageEntity(
sender = sender.id,
coupon = fromCoupon(coupon),
message = message,
enrollDate = enrollDate,
enrollEndDate = enrollDate.plus(enrollTerm.term, enrollTerm.unit),
enrollCode = giftCouponCode.code
)
)
return GiftMessage(
sender = sender,
giftMessage = giftMessageEntity.message,
giftCouponCode = GiftCouponCode(giftMessageEntity.enrollCode),
giftCoupon = GiftCoupon(coupon)
giftCoupon = GiftCoupon(coupon, giftMessageEntity.enrollEndDate, giftMessageEntity.isUsed)
)
}

Expand All @@ -51,7 +61,7 @@ class GiftCouponMessageAdapter(

override fun findGiftCoupon(giftCouponCode: GiftCouponCode): GiftCoupon {
return giftMessageRepository.findByEnrollCode(giftCouponCode.code)
?.let { GiftCoupon(toCoupon(it.coupon), it.isUsed) }
?.let { GiftCoupon(toCoupon(it.coupon), it.enrollEndDate, it.isUsed) }
?: throw IllegalArgumentException("GiftCouponCode not found")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.example.estdelivery.coupon.application.port.out.adapter.persistence.entity

import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.Table
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit

@Entity
@Table(name = "enroll_term")
class EnrollTermEntity(
@Column(updatable = false)
val term: Long,
@Enumerated(value = EnumType.STRING)
val unit: ChronoUnit,
@Column(updatable = false)
val createDate: LocalDateTime = LocalDateTime.now(),
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class GiftMessageEntity(
val coupon: CouponEntity,
@Column(nullable = false)
val enrollDate: LocalDate = LocalDate.now(),
@Column(nullable = false)
val enrollEndDate: LocalDate,
var isUsed: Boolean = false,
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.example.estdelivery.coupon.application.port.out.adapter.persistence.repository

import com.example.estdelivery.coupon.application.port.out.adapter.persistence.entity.EnrollTermEntity
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository

interface EnrollDateJpaRepository : JpaRepository<EnrollTermEntity, Long> {
@Query("SELECT e FROM EnrollTermEntity e WHERE e.id = (SELECT MAX(e2.id) FROM EnrollTermEntity e2)")
fun findLatestPolicy(): EnrollTermEntity?
}

@Repository
class EnrollDateRepository(
private val repository: EnrollDateJpaRepository
) {
fun findLatestPolicy(): EnrollTermEntity? {
return repository.findLatestPolicy()
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.example.estdelivery.coupon.domain.coupon

import java.time.LocalDate

/**
* 선물하는 쿠폰을 선정할 때 여러 제약이 생길 수 있다.
* 제약 변경에 대응하기 위해 컴포지션을 활용한다.
*/
data class GiftCoupon(
val coupon: Coupon,
val enrollEndDate: LocalDate,
val isUsed: Boolean = false,
) {
init {
require(!coupon.isPublished()) { "발행한 쿠폰은 선물할 수 없습니다." }
require(enrollEndDate.isAfter(LocalDate.now())) { "유효기간이 지난 쿠폰은 선물할 수 없습니다." }
require(!isUsed) { "이미 사용된 쿠폰은 선물할 수 없습니다." }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import com.example.estdelivery.coupon.domain.coupon.GiftCouponCode
import com.example.estdelivery.coupon.domain.fixture.나눠준_비율_할인_쿠폰
import com.example.estdelivery.coupon.domain.fixture.일건창
import com.example.estdelivery.coupon.domain.member.Member
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.collections.shouldContain
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import java.time.LocalDate

class EnrollCouponByMessageServiceTest : FreeSpec({
val loadMemberStatePort = mockk<LoadMemberStatePort>()
Expand All @@ -40,7 +40,7 @@ class EnrollCouponByMessageServiceTest : FreeSpec({
val member = 일건창()
val giftCouponCode = GiftCouponCode.create()
val coupon = 나눠준_비율_할인_쿠폰
val giftCoupon = GiftCoupon(coupon)
val giftCoupon = GiftCoupon(coupon, LocalDate.now().plusDays(1))
val updatedMember = slot<Member>()

// when
Expand All @@ -54,21 +54,4 @@ class EnrollCouponByMessageServiceTest : FreeSpec({
// then
updatedMember.captured.showMyCouponBook() shouldContain coupon
}

"메시지로 받은 쿠폰 코드는 사용된적이 없으면 예외가 발생한다." {
// given
val member = 일건창()
val giftCouponCode = GiftCouponCode.create()
val coupon = 나눠준_비율_할인_쿠폰
val giftCoupon = GiftCoupon(coupon, true)

// when
every { loadMemberStatePort.findMember(member.id) } returns member
every { loadGiftCouponStatePort.findGiftCoupon(giftCouponCode) } returns giftCoupon

// then
shouldThrow<IllegalArgumentException> {
enrollCouponByMessageService.enroll(member.id, giftCouponCode)
}
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ class FindAvailableGiftCouponServiceTest : FreeSpec({
val availableGiftCoupons = findAvailableGiftCouponService.findAvailableGiftCoupon(memberId)

// then
availableGiftCoupons.map { it.coupon } shouldBe 일건창.showMyCouponBook().filter { !it.isPublished() }
availableGiftCoupons.coupons.map { it.id } shouldBe 일건창.showMyCouponBook().filter { !it.isPublished() }.map { it.id }
}
})
Loading
Loading