feat(gift): 관리자 선물함 HTTP 조회를 추가한다
This commit is contained in:
+23
-1
@@ -3,12 +3,16 @@ package kr.co.vividnext.sodalive.v2.api.admin.gift.adapter.`in`.web
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.admin.gift.dto.AdminGiftCategoryRequest
|
||||
import kr.co.vividnext.sodalive.v2.api.admin.gift.dto.AdminGiftCategoryResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.admin.gift.dto.AdminGiftDetailResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.admin.gift.dto.AdminGiftListResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.admin.gift.dto.AdminGiftMarkUndeliverableRequest
|
||||
import kr.co.vividnext.sodalive.v2.api.admin.gift.dto.AdminGiftOperationStatusResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.admin.gift.dto.AdminGiftSizePriceRequest
|
||||
import kr.co.vividnext.sodalive.v2.api.admin.gift.dto.AdminGiftSizePriceResponse
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.GiftAdminQueryService
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.GiftAdminService
|
||||
import kr.co.vividnext.sodalive.v2.gift.domain.GiftSize
|
||||
import kr.co.vividnext.sodalive.v2.gift.domain.GiftStatus
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.DeleteMapping
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
@@ -17,12 +21,30 @@ import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/admin")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
class AdminGiftController(private val service: GiftAdminService) {
|
||||
class AdminGiftController(
|
||||
private val service: GiftAdminService,
|
||||
private val queryService: GiftAdminQueryService
|
||||
) {
|
||||
@GetMapping("/gifts")
|
||||
fun getGifts(
|
||||
@RequestParam(required = false) status: GiftStatus?,
|
||||
@RequestParam(required = false) applicationNo: String?,
|
||||
@RequestParam(required = false) nickname: String?,
|
||||
@RequestParam(defaultValue = "0") page: Int,
|
||||
@RequestParam(defaultValue = "20") size: Int
|
||||
) = ApiResponse.ok(AdminGiftListResponse.from(queryService.getGifts(status, applicationNo, nickname, page, size)))
|
||||
|
||||
@GetMapping("/gifts/{applicationNo}")
|
||||
fun getGiftDetail(@PathVariable applicationNo: String) = ApiResponse.ok(
|
||||
AdminGiftDetailResponse.from(queryService.getGiftDetail(applicationNo))
|
||||
)
|
||||
|
||||
@GetMapping("/gift-categories")
|
||||
fun getCategories() = ApiResponse.ok(service.getCategories().map { AdminGiftCategoryResponse.from(it) })
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.admin.gift.dto
|
||||
|
||||
import kr.co.vividnext.sodalive.extensions.toUtcIso
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.AdminGiftDetailResult
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.AdminGiftInboundDeliveryInfoResult
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.AdminGiftListItemResult
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.AdminGiftListResult
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.AdminGiftMemberInfoResult
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.AdminGiftProductInfoResult
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.GiftCategoryResult
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.GiftOperationStatusResult
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.GiftSizePriceResult
|
||||
@@ -63,3 +69,121 @@ data class AdminGiftOperationStatusResponse(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class AdminGiftListResponse(
|
||||
val totalCount: Long,
|
||||
val items: List<AdminGiftListItemResponse>,
|
||||
val page: Int,
|
||||
val size: Int,
|
||||
val hasNext: Boolean
|
||||
) {
|
||||
companion object {
|
||||
fun from(result: AdminGiftListResult) = AdminGiftListResponse(
|
||||
totalCount = result.totalCount,
|
||||
items = result.items.map { AdminGiftListItemResponse.from(it) },
|
||||
page = result.page,
|
||||
size = result.size,
|
||||
hasNext = result.hasNext
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class AdminGiftListItemResponse(
|
||||
val applicationNo: String,
|
||||
val senderNickname: String,
|
||||
val recipientNickname: String,
|
||||
val sizeCode: String,
|
||||
val sizeName: String,
|
||||
val categoryName: String,
|
||||
val classificationNumber: String,
|
||||
val courierCompanyName: String?,
|
||||
val trackingNumber: String?,
|
||||
val status: GiftStatus,
|
||||
val statusName: String,
|
||||
val availableActions: List<String>
|
||||
) {
|
||||
companion object {
|
||||
fun from(result: AdminGiftListItemResult) = AdminGiftListItemResponse(
|
||||
applicationNo = result.applicationNo,
|
||||
senderNickname = result.senderNickname,
|
||||
recipientNickname = result.recipientNickname,
|
||||
sizeCode = result.sizeCode,
|
||||
sizeName = result.sizeName,
|
||||
categoryName = result.categoryName,
|
||||
classificationNumber = result.classificationNumber,
|
||||
courierCompanyName = result.courierCompanyName,
|
||||
trackingNumber = result.trackingNumber,
|
||||
status = result.status,
|
||||
statusName = result.statusName,
|
||||
availableActions = result.availableActions
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class AdminGiftDetailResponse(
|
||||
val applicationNo: String,
|
||||
val senderInfo: AdminGiftMemberInfoResponse,
|
||||
val recipientInfo: AdminGiftMemberInfoResponse,
|
||||
val productInfo: AdminGiftProductInfoResponse,
|
||||
val inboundDeliveryInfo: AdminGiftInboundDeliveryInfoResponse,
|
||||
val status: GiftStatus,
|
||||
val statusName: String,
|
||||
val availableActions: List<String>
|
||||
) {
|
||||
companion object {
|
||||
fun from(result: AdminGiftDetailResult) = AdminGiftDetailResponse(
|
||||
applicationNo = result.applicationNo,
|
||||
senderInfo = AdminGiftMemberInfoResponse.from(result.senderInfo),
|
||||
recipientInfo = AdminGiftMemberInfoResponse.from(result.recipientInfo),
|
||||
productInfo = AdminGiftProductInfoResponse.from(result.productInfo),
|
||||
inboundDeliveryInfo = AdminGiftInboundDeliveryInfoResponse.from(result.inboundDeliveryInfo),
|
||||
status = result.status,
|
||||
statusName = result.statusName,
|
||||
availableActions = result.availableActions
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class AdminGiftMemberInfoResponse(
|
||||
val nickname: String,
|
||||
val name: String,
|
||||
val phoneNumber: String,
|
||||
val address: String
|
||||
) {
|
||||
companion object {
|
||||
fun from(result: AdminGiftMemberInfoResult) = AdminGiftMemberInfoResponse(
|
||||
nickname = result.nickname,
|
||||
name = result.name,
|
||||
phoneNumber = result.phoneNumber,
|
||||
address = result.address
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class AdminGiftProductInfoResponse(
|
||||
val sizeCode: String,
|
||||
val sizeName: String,
|
||||
val categoryName: String,
|
||||
val classificationNumber: String
|
||||
) {
|
||||
companion object {
|
||||
fun from(result: AdminGiftProductInfoResult) = AdminGiftProductInfoResponse(
|
||||
sizeCode = result.sizeCode,
|
||||
sizeName = result.sizeName,
|
||||
categoryName = result.categoryName,
|
||||
classificationNumber = result.classificationNumber
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class AdminGiftInboundDeliveryInfoResponse(
|
||||
val courierCompanyName: String?,
|
||||
val trackingNumber: String?
|
||||
) {
|
||||
companion object {
|
||||
fun from(result: AdminGiftInboundDeliveryInfoResult) = AdminGiftInboundDeliveryInfoResponse(
|
||||
courierCompanyName = result.courierCompanyName,
|
||||
trackingNumber = result.trackingNumber
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+141
-1
@@ -1,7 +1,10 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.admin.gift.adapter.`in`.web
|
||||
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.Gift
|
||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftCategory
|
||||
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
|
||||
@@ -46,12 +49,93 @@ class AdminGiftControllerTest @Autowired constructor(
|
||||
private val categoryRepository: GiftCategoryRepository,
|
||||
private val sizePriceRepository: GiftSizePriceRepository,
|
||||
private val giftRepository: GiftRepository,
|
||||
private val giftDeliveryRepository: GiftDeliveryRepository
|
||||
private val giftDeliveryRepository: GiftDeliveryRepository,
|
||||
private val memberRepository: MemberRepository
|
||||
) {
|
||||
@BeforeEach
|
||||
fun deleteGiftFixtures() {
|
||||
giftDeliveryRepository.deleteAll()
|
||||
giftRepository.deleteAll()
|
||||
categoryRepository.deleteAll()
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("관리자는 전체 선물함 목록을 필터링해 조회한다")
|
||||
fun shouldListAdminGifts() {
|
||||
val (gift, _) = saveAdminGift(
|
||||
applicationNo = "A-1002609300001",
|
||||
status = GiftStatus.TRACKING_REGISTERED,
|
||||
senderNickname = "admin-fan-alpha",
|
||||
recipientNickname = "admin-creator-alpha",
|
||||
withTracking = true
|
||||
)
|
||||
|
||||
mockMvc.perform(
|
||||
get("/api/v2/admin/gifts")
|
||||
.with(user("admin").roles("ADMIN"))
|
||||
.param("status", "TRACKING_REGISTERED")
|
||||
.param("applicationNo", "0001")
|
||||
.param("nickname", "alpha")
|
||||
.param("page", "0")
|
||||
.param("size", "20")
|
||||
)
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.andExpect(jsonPath("$.data.totalCount").value(1))
|
||||
.andExpect(jsonPath("$.data.items[0].applicationNo").value(gift.applicationNo))
|
||||
.andExpect(jsonPath("$.data.items[0].senderNickname").value("admin-fan-alpha"))
|
||||
.andExpect(jsonPath("$.data.items[0].recipientNickname").value("admin-creator-alpha"))
|
||||
.andExpect(jsonPath("$.data.items[0].sizeCode").value("SMALL"))
|
||||
.andExpect(jsonPath("$.data.items[0].sizeName").value("소형"))
|
||||
.andExpect(jsonPath("$.data.items[0].categoryName").value("인형"))
|
||||
.andExpect(jsonPath("$.data.items[0].classificationNumber").value("100"))
|
||||
.andExpect(jsonPath("$.data.items[0].courierCompanyName").value("CJ대한통운"))
|
||||
.andExpect(jsonPath("$.data.items[0].trackingNumber").value("1234567890"))
|
||||
.andExpect(jsonPath("$.data.items[0].status").value("TRACKING_REGISTERED"))
|
||||
.andExpect(jsonPath("$.data.items[0].statusName").value("발송 확인"))
|
||||
.andExpect(jsonPath("$.data.items[0].availableActions[0]").value("ARRIVE_MAILBOX"))
|
||||
.andExpect(jsonPath("$.data.items[0].availableActions[1]").value("MARK_UNDELIVERABLE"))
|
||||
.andExpect(jsonPath("$.data.page").value(0))
|
||||
.andExpect(jsonPath("$.data.size").value(20))
|
||||
.andExpect(jsonPath("$.data.hasNext").value(false))
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("관리자는 선물 상세를 조회하고 미입력 수취인 정보는 빈 문자열로 받는다")
|
||||
fun shouldGetAdminGiftDetail() {
|
||||
saveAdminGift(
|
||||
applicationNo = "A-1002609300002",
|
||||
status = GiftStatus.ARRIVED_AT_MAILBOX,
|
||||
senderNickname = "admin-fan-beta",
|
||||
recipientNickname = "admin-creator-beta",
|
||||
withTracking = true
|
||||
)
|
||||
|
||||
mockMvc.perform(
|
||||
get("/api/v2/admin/gifts/A-1002609300002")
|
||||
.with(user("admin").roles("ADMIN"))
|
||||
)
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.andExpect(jsonPath("$.data.applicationNo").value("A-1002609300002"))
|
||||
.andExpect(jsonPath("$.data.senderInfo.nickname").value("admin-fan-beta"))
|
||||
.andExpect(jsonPath("$.data.senderInfo.name").value("홍길동"))
|
||||
.andExpect(jsonPath("$.data.senderInfo.phoneNumber").value("01012345678"))
|
||||
.andExpect(jsonPath("$.data.senderInfo.address").value("(06234) 서울시 강남구, 101동 1001호"))
|
||||
.andExpect(jsonPath("$.data.recipientInfo.nickname").value("admin-creator-beta"))
|
||||
.andExpect(jsonPath("$.data.recipientInfo.name").value(""))
|
||||
.andExpect(jsonPath("$.data.recipientInfo.phoneNumber").value(""))
|
||||
.andExpect(jsonPath("$.data.recipientInfo.address").value(""))
|
||||
.andExpect(jsonPath("$.data.productInfo.sizeCode").value("SMALL"))
|
||||
.andExpect(jsonPath("$.data.productInfo.sizeName").value("소형"))
|
||||
.andExpect(jsonPath("$.data.productInfo.categoryName").value("인형"))
|
||||
.andExpect(jsonPath("$.data.productInfo.classificationNumber").value("100"))
|
||||
.andExpect(jsonPath("$.data.inboundDeliveryInfo.courierCompanyName").value("CJ대한통운"))
|
||||
.andExpect(jsonPath("$.data.inboundDeliveryInfo.trackingNumber").value("1234567890"))
|
||||
.andExpect(jsonPath("$.data.status").value("ARRIVED_AT_MAILBOX"))
|
||||
.andExpect(jsonPath("$.data.statusName").value("사서함 도착"))
|
||||
.andExpect(jsonPath("$.data.availableActions[0]").value("COMPLETE_INSPECTION"))
|
||||
.andExpect(jsonPath("$.data.availableActions[1]").value("MARK_UNDELIVERABLE"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -242,6 +326,62 @@ class AdminGiftControllerTest @Autowired constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun saveAdminGift(
|
||||
applicationNo: String,
|
||||
status: GiftStatus,
|
||||
senderNickname: String,
|
||||
recipientNickname: String,
|
||||
withTracking: Boolean
|
||||
): Pair<Gift, GiftDelivery> {
|
||||
val sender = memberRepository.save(Member(password = "password", nickname = senderNickname))
|
||||
val recipient = memberRepository.save(Member(password = "password", nickname = recipientNickname))
|
||||
val category = categoryRepository.save(
|
||||
GiftCategory(
|
||||
classificationNumber = "100",
|
||||
categoryCode = "ADMIN_${applicationNo.takeLast(4)}",
|
||||
name = "인형",
|
||||
receiptCode = "A",
|
||||
representativeItem = "피규어"
|
||||
)
|
||||
)
|
||||
val gift = giftRepository.save(
|
||||
Gift(
|
||||
applicationNo = applicationNo,
|
||||
senderMemberId = sender.id!!,
|
||||
recipientMemberId = recipient.id!!,
|
||||
status = status,
|
||||
sizeCode = GiftSize.SMALL,
|
||||
categoryId = category.id!!,
|
||||
categoryNameSnapshot = category.name,
|
||||
salePriceCan = 80,
|
||||
canUsageId = 10L,
|
||||
senderTermsAgreed = true,
|
||||
senderPrivacyAgreed = true,
|
||||
senderTermsAgreedAt = NOW.minusDays(1),
|
||||
senderPrivacyAgreedAt = NOW.minusDays(1),
|
||||
damageWaiverAgreed = true
|
||||
)
|
||||
)
|
||||
val delivery = giftDeliveryRepository.save(
|
||||
GiftDelivery(
|
||||
giftId = requireNotNull(gift.id),
|
||||
senderName = "홍길동",
|
||||
senderPhoneNumber = "01012345678",
|
||||
senderZipCode = "06234",
|
||||
senderAddress = "서울시 강남구",
|
||||
senderAddressDetail = "101동 1001호",
|
||||
trackingDeadlineAt = NOW.minusDays(1)
|
||||
).apply {
|
||||
if (withTracking) {
|
||||
courierCompanyName = "CJ대한통운"
|
||||
trackingNumber = "1234567890"
|
||||
trackingRegisteredAt = NOW.minusHours(1)
|
||||
}
|
||||
}
|
||||
)
|
||||
return gift to delivery
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val APPLICATION_NO = "G20260929000001"
|
||||
const val SENDER_MEMBER_ID = 1L
|
||||
|
||||
Reference in New Issue
Block a user