feat(gift): 선물 배송과 리뷰 저장소를 추가한다

This commit is contained in:
2026-09-30 13:35:31 +09:00
parent 21d6e755e9
commit 8ae722c8df
5 changed files with 267 additions and 0 deletions
@@ -0,0 +1,87 @@
package kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence
import kr.co.vividnext.sodalive.common.BaseEntity
import java.time.LocalDateTime
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Table
import javax.persistence.UniqueConstraint
@Entity
@Table(
name = "gift_delivery",
uniqueConstraints = [UniqueConstraint(name = "uk_gift_delivery_gift_id", columnNames = ["gift_id"])]
)
class GiftDelivery(
@Column(name = "gift_id", nullable = false)
val giftId: Long,
@Column(name = "sender_name", nullable = false, length = 50)
val senderName: String,
@Column(name = "sender_phone_number", nullable = false, length = 30)
val senderPhoneNumber: String,
@Column(name = "sender_zip_code", nullable = false, length = 20)
val senderZipCode: String,
@Column(name = "sender_address", nullable = false, length = 255)
val senderAddress: String,
@Column(name = "sender_address_detail", length = 255)
val senderAddressDetail: String? = null,
@Column(name = "recipient_name", length = 50)
var recipientName: String? = null,
@Column(name = "recipient_phone_number", length = 30)
var recipientPhoneNumber: String? = null,
@Column(name = "recipient_zip_code", length = 20)
var recipientZipCode: String? = null,
@Column(name = "recipient_address", length = 255)
var recipientAddress: String? = null,
@Column(name = "recipient_address_detail", length = 255)
var recipientAddressDetail: String? = null,
@Column(name = "courier_company_name", length = 50)
var courierCompanyName: String? = null,
@Column(name = "tracking_number", length = 100)
var trackingNumber: String? = null,
@Column(name = "tracking_deadline_at", nullable = false)
val trackingDeadlineAt: LocalDateTime,
@Column(name = "recipient_address_deadline_at")
var recipientAddressDeadlineAt: LocalDateTime? = null,
@Column(name = "tracking_deadline_reminder_sent_at")
var trackingDeadlineReminderSentAt: LocalDateTime? = null,
@Column(name = "recipient_address_deadline_reminder_sent_at")
var recipientAddressDeadlineReminderSentAt: LocalDateTime? = null,
@Column(name = "tracking_registered_at")
var trackingRegisteredAt: LocalDateTime? = null,
@Column(name = "arrived_at_mailbox_at")
var arrivedAtMailboxAt: LocalDateTime? = null,
@Column(name = "inspection_completed_at")
var inspectionCompletedAt: LocalDateTime? = null,
@Column(name = "delivered_at")
var deliveredAt: LocalDateTime? = null,
@Column(name = "canceled_at")
var canceledAt: LocalDateTime? = null,
@Column(name = "undeliverable_at")
var undeliverableAt: LocalDateTime? = null,
@Column(name = "undeliverable_reason", length = 255)
var undeliverableReason: String? = null
) : BaseEntity()
@@ -0,0 +1,7 @@
package kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence
import org.springframework.data.jpa.repository.JpaRepository
interface GiftDeliveryRepository : JpaRepository<GiftDelivery, Long> {
fun findByGiftId(giftId: Long): GiftDelivery?
}
@@ -0,0 +1,29 @@
package kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence
import kr.co.vividnext.sodalive.common.BaseEntity
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Table
import javax.persistence.UniqueConstraint
@Entity
@Table(
name = "gift_review",
uniqueConstraints = [UniqueConstraint(name = "uk_gift_review_gift_id", columnNames = ["gift_id"])]
)
class GiftReview(
@Column(name = "gift_id", nullable = false)
val giftId: Long,
@Column(name = "sender_member_id", nullable = false)
val senderMemberId: Long,
@Column(name = "rating", nullable = false)
val rating: Int,
@Column(name = "keywords", columnDefinition = "text")
var keywords: String? = null,
@Column(name = "comment", length = 255)
var comment: String? = null
) : BaseEntity()
@@ -0,0 +1,7 @@
package kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence
import org.springframework.data.jpa.repository.JpaRepository
interface GiftReviewRepository : JpaRepository<GiftReview, Long> {
fun findByGiftId(giftId: Long): GiftReview?
}
@@ -0,0 +1,137 @@
package kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence
import kr.co.vividnext.sodalive.configs.QueryDslConfig
import kr.co.vividnext.sodalive.v2.gift.domain.GiftSize
import kr.co.vividnext.sodalive.v2.gift.domain.GiftStatus
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.context.annotation.Import
import org.springframework.dao.DataIntegrityViolationException
import java.time.LocalDateTime
@DataJpaTest(
properties = [
"spring.cache.type=none",
"spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;NON_KEYWORDS=VALUE"
]
)
@Import(QueryDslConfig::class)
@DisplayName("선물 엔티티 매핑 테스트")
class GiftEntityMappingTest @Autowired constructor(
private val giftRepository: GiftRepository,
private val giftDeliveryRepository: GiftDeliveryRepository,
private val giftCategoryRepository: GiftCategoryRepository,
private val giftSizePriceRepository: GiftSizePriceRepository,
private val giftReviewRepository: GiftReviewRepository,
private val giftApplicationNoSequenceRepository: GiftApplicationNoSequenceRepository
) {
@Test
@DisplayName("선물 엔티티와 배송 정보를 저장하고 조회한다")
fun shouldSaveAndFindGiftWithDelivery() {
val category = giftCategoryRepository.save(
GiftCategory(
categoryCode = "DOLL",
name = "인형",
requiresDamageWaiver = true
)
)
giftSizePriceRepository.save(
GiftSizePrice(
sizeCode = GiftSize.SMALL,
basePriceCan = 100,
salePriceCan = 80
)
)
val gift = giftRepository.save(
Gift(
applicationNo = "G20260929000001",
senderMemberId = 1L,
recipientMemberId = 2L,
status = GiftStatus.RECEIVED,
sizeCode = GiftSize.SMALL,
categoryId = category.id!!,
categoryNameSnapshot = category.name,
salePriceCan = 80,
canUsageId = 10L,
senderTermsAgreed = true,
senderPrivacyAgreed = true,
senderTermsAgreedAt = LocalDateTime.of(2026, 9, 29, 12, 0),
senderPrivacyAgreedAt = LocalDateTime.of(2026, 9, 29, 12, 0),
damageWaiverAgreed = true
)
)
giftDeliveryRepository.save(
GiftDelivery(
giftId = gift.id!!,
senderName = "홍길동",
senderPhoneNumber = "01012345678",
senderZipCode = "06234",
senderAddress = "서울시 강남구",
senderAddressDetail = "101동 1001호",
trackingDeadlineAt = LocalDateTime.of(2026, 10, 2, 12, 0)
)
)
val saved = giftRepository.findByApplicationNo("G20260929000001")
val delivery = giftDeliveryRepository.findByGiftId(gift.id!!)
assertNotNull(saved)
assertEquals(GiftStatus.RECEIVED, saved!!.status)
assertEquals("홍길동", delivery!!.senderName)
assertNotNull(saved.createdAt)
assertNotNull(saved.updatedAt)
}
@Test
@DisplayName("신청번호와 카테고리 코드와 리뷰 선물 ID는 중복 저장할 수 없다")
fun shouldRejectDuplicateUniqueValues() {
val category = giftCategoryRepository.save(GiftCategory(categoryCode = "DOLL", name = "인형"))
val gift = giftRepository.save(gift(category.id!!, "G20260929000001"))
giftRepository.saveAndFlush(gift(category.id!!, "G20260929000002"))
giftReviewRepository.saveAndFlush(GiftReview(giftId = gift.id!!, senderMemberId = 1L, rating = 5))
assertThrows(DataIntegrityViolationException::class.java) {
giftRepository.saveAndFlush(gift(category.id!!, "G20260929000001"))
}
assertThrows(DataIntegrityViolationException::class.java) {
giftCategoryRepository.saveAndFlush(GiftCategory(categoryCode = "DOLL", name = "중복"))
}
assertThrows(DataIntegrityViolationException::class.java) {
giftReviewRepository.saveAndFlush(GiftReview(giftId = gift.id!!, senderMemberId = 1L, rating = 4))
}
}
@Test
@DisplayName("날짜별 신청번호 채번 sequence를 저장한다")
fun shouldSaveApplicationNoSequence() {
val sequence = giftApplicationNoSequenceRepository.save(
GiftApplicationNoSequence(sequenceDate = "20260929", lastSequence = 1)
)
assertEquals("20260929", sequence.sequenceDate)
assertEquals(1, sequence.lastSequence)
}
private fun gift(categoryId: Long, applicationNo: String) = Gift(
applicationNo = applicationNo,
senderMemberId = 1L,
recipientMemberId = 2L,
status = GiftStatus.RECEIVED,
sizeCode = GiftSize.SMALL,
categoryId = categoryId,
categoryNameSnapshot = "인형",
salePriceCan = 80,
canUsageId = 10L,
senderTermsAgreed = true,
senderPrivacyAgreed = true,
senderTermsAgreedAt = LocalDateTime.of(2026, 9, 29, 12, 0),
senderPrivacyAgreedAt = LocalDateTime.of(2026, 9, 29, 12, 0),
damageWaiverAgreed = true
)
}