feat(agent-read): 관리자 에이전트 조회 API를 추가한다
This commit is contained in:
@@ -0,0 +1,118 @@
|
|||||||
|
package kr.co.vividnext.sodalive.admin.partner.agent.read
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import org.springframework.data.domain.Pageable
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam
|
||||||
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
@RequestMapping("/admin/partner/agent")
|
||||||
|
class AdminAgentReadController(
|
||||||
|
private val service: AdminAgentReadService
|
||||||
|
) {
|
||||||
|
@GetMapping("/list")
|
||||||
|
fun getAgentList(pageable: Pageable) = ApiResponse.ok(
|
||||||
|
service.getAgentList(offset = pageable.offset, limit = pageable.pageSize.toLong())
|
||||||
|
)
|
||||||
|
|
||||||
|
@GetMapping("/creator/search")
|
||||||
|
fun searchAssignableCreators(
|
||||||
|
@RequestParam(value = "search_word") searchWord: String,
|
||||||
|
pageable: Pageable
|
||||||
|
) = ApiResponse.ok(
|
||||||
|
service.searchAssignableCreators(searchWord = searchWord, offset = pageable.offset, limit = pageable.pageSize.toLong())
|
||||||
|
)
|
||||||
|
|
||||||
|
@GetMapping("/{agentId}/creator/list")
|
||||||
|
fun getAssignedCreators(
|
||||||
|
@PathVariable agentId: Long,
|
||||||
|
pageable: Pageable
|
||||||
|
) = ApiResponse.ok(
|
||||||
|
service.getAssignedCreators(agentId = agentId, offset = pageable.offset, limit = pageable.pageSize.toLong())
|
||||||
|
)
|
||||||
|
|
||||||
|
@GetMapping("/{agentId}/calculate/live-by-creator")
|
||||||
|
fun getCalculateLiveByCreator(
|
||||||
|
@PathVariable agentId: Long,
|
||||||
|
@RequestParam startDateStr: String,
|
||||||
|
@RequestParam endDateStr: String,
|
||||||
|
pageable: Pageable
|
||||||
|
) = ApiResponse.ok(
|
||||||
|
service.getCalculateLiveByCreator(
|
||||||
|
agentId,
|
||||||
|
startDateStr,
|
||||||
|
endDateStr,
|
||||||
|
pageable.offset,
|
||||||
|
pageable.pageSize.toLong()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@GetMapping("/{agentId}/calculate/content-by-creator")
|
||||||
|
fun getCalculateContentByCreator(
|
||||||
|
@PathVariable agentId: Long,
|
||||||
|
@RequestParam startDateStr: String,
|
||||||
|
@RequestParam endDateStr: String,
|
||||||
|
pageable: Pageable
|
||||||
|
) = ApiResponse.ok(
|
||||||
|
service.getCalculateContentByCreator(
|
||||||
|
agentId,
|
||||||
|
startDateStr,
|
||||||
|
endDateStr,
|
||||||
|
pageable.offset,
|
||||||
|
pageable.pageSize.toLong()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@GetMapping("/{agentId}/calculate/community-by-creator")
|
||||||
|
fun getCalculateCommunityByCreator(
|
||||||
|
@PathVariable agentId: Long,
|
||||||
|
@RequestParam startDateStr: String,
|
||||||
|
@RequestParam endDateStr: String,
|
||||||
|
pageable: Pageable
|
||||||
|
) = ApiResponse.ok(
|
||||||
|
service.getCalculateCommunityByCreator(
|
||||||
|
agentId,
|
||||||
|
startDateStr,
|
||||||
|
endDateStr,
|
||||||
|
pageable.offset,
|
||||||
|
pageable.pageSize.toLong()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@GetMapping("/{agentId}/calculate/channel-donation-by-creator")
|
||||||
|
fun getChannelDonationByCreator(
|
||||||
|
@PathVariable agentId: Long,
|
||||||
|
@RequestParam startDateStr: String,
|
||||||
|
@RequestParam endDateStr: String,
|
||||||
|
pageable: Pageable
|
||||||
|
) = ApiResponse.ok(
|
||||||
|
service.getChannelDonationByCreator(
|
||||||
|
agentId,
|
||||||
|
startDateStr,
|
||||||
|
endDateStr,
|
||||||
|
pageable.offset,
|
||||||
|
pageable.pageSize.toLong()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@GetMapping("/{agentId}/calculate/content-donation-by-creator")
|
||||||
|
fun getCalculateContentDonationByCreator(
|
||||||
|
@PathVariable agentId: Long,
|
||||||
|
@RequestParam startDateStr: String,
|
||||||
|
@RequestParam endDateStr: String,
|
||||||
|
pageable: Pageable
|
||||||
|
) = ApiResponse.ok(
|
||||||
|
service.getCalculateContentDonationByCreator(
|
||||||
|
agentId,
|
||||||
|
startDateStr,
|
||||||
|
endDateStr,
|
||||||
|
pageable.offset,
|
||||||
|
pageable.pageSize.toLong()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package kr.co.vividnext.sodalive.admin.partner.agent.read
|
||||||
|
|
||||||
|
import com.querydsl.core.types.dsl.BooleanExpression
|
||||||
|
import com.querydsl.core.types.dsl.Expressions
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
import kr.co.vividnext.sodalive.member.QMember
|
||||||
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.assignment.QAgentCreatorRelation.agentCreatorRelation
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class AdminAgentReadQueryRepository(
|
||||||
|
private val queryFactory: JPAQueryFactory,
|
||||||
|
private val calculateQueryRepository: AgentCalculateQueryRepository
|
||||||
|
) {
|
||||||
|
private val kstZoneId = ZoneId.of("Asia/Seoul")
|
||||||
|
private val utcZoneId = ZoneId.of("UTC")
|
||||||
|
|
||||||
|
fun getAgentListTotalCount(): Int {
|
||||||
|
return queryFactory
|
||||||
|
.select(member.id.count())
|
||||||
|
.from(member)
|
||||||
|
.where(member.role.eq(MemberRole.AGENT))
|
||||||
|
.fetchOne()
|
||||||
|
?.toInt()
|
||||||
|
?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAgentList(offset: Long, limit: Long, currentTime: ZonedDateTime): List<GetAdminAgentListItem> {
|
||||||
|
val kstCurrentTime = currentTime.withZoneSameInstant(kstZoneId)
|
||||||
|
val currentMonthStartKst = kstCurrentTime.toLocalDate().withDayOfMonth(1).atStartOfDay(kstZoneId)
|
||||||
|
val nextMonthStartKst = currentMonthStartKst.plusMonths(1)
|
||||||
|
val currentMonthStart = currentMonthStartKst.withZoneSameInstant(utcZoneId).toLocalDateTime()
|
||||||
|
val currentMonthEnd = nextMonthStartKst.withZoneSameInstant(utcZoneId).toLocalDateTime().minusNanos(1)
|
||||||
|
|
||||||
|
return queryFactory
|
||||||
|
.select(
|
||||||
|
QGetAdminAgentListItem(
|
||||||
|
member.id,
|
||||||
|
member.nickname,
|
||||||
|
agentCreatorRelation.id.countDistinct().intValue(),
|
||||||
|
Expressions.numberTemplate(Int::class.java, "0"),
|
||||||
|
Expressions.numberTemplate(Int::class.java, "0"),
|
||||||
|
Expressions.numberTemplate(Int::class.java, "0"),
|
||||||
|
Expressions.numberTemplate(Int::class.java, "0"),
|
||||||
|
Expressions.numberTemplate(Int::class.java, "0")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.from(member)
|
||||||
|
.leftJoin(agentCreatorRelation)
|
||||||
|
.on(
|
||||||
|
agentCreatorRelation.agent.id.eq(member.id)
|
||||||
|
.and(isActiveAssignment(kstCurrentTime.toLocalDateTime()))
|
||||||
|
)
|
||||||
|
.where(member.role.eq(MemberRole.AGENT))
|
||||||
|
.groupBy(member.id, member.nickname)
|
||||||
|
.orderBy(member.id.desc())
|
||||||
|
.offset(offset)
|
||||||
|
.limit(limit)
|
||||||
|
.fetch()
|
||||||
|
.map { item ->
|
||||||
|
item.copy(
|
||||||
|
liveAgentSettlementAmount = calculateQueryRepository
|
||||||
|
.getCalculateLiveByCreatorTotal(currentMonthStart, currentMonthEnd, item.agentId)
|
||||||
|
.agentSettlementAmount,
|
||||||
|
contentAgentSettlementAmount = calculateQueryRepository
|
||||||
|
.getCalculateContentByCreatorTotal(currentMonthStart, currentMonthEnd, item.agentId)
|
||||||
|
.agentSettlementAmount,
|
||||||
|
communityAgentSettlementAmount = calculateQueryRepository
|
||||||
|
.getCalculateCommunityByCreatorTotal(currentMonthStart, currentMonthEnd, item.agentId)
|
||||||
|
.agentSettlementAmount,
|
||||||
|
contentDonationAgentSettlementAmount = calculateQueryRepository
|
||||||
|
.getCalculateContentDonationByCreatorTotal(currentMonthStart, currentMonthEnd, item.agentId)
|
||||||
|
.agentSettlementAmount,
|
||||||
|
channelDonationAgentSettlementAmount = calculateQueryRepository
|
||||||
|
.getChannelDonationByCreatorTotal(currentMonthStart, currentMonthEnd, item.agentId)
|
||||||
|
.agentSettlementAmount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchAssignableCreatorsTotalCount(searchWord: String, currentTime: LocalDateTime): Int {
|
||||||
|
return queryFactory
|
||||||
|
.select(member.id.countDistinct())
|
||||||
|
.from(member)
|
||||||
|
.leftJoin(agentCreatorRelation)
|
||||||
|
.on(
|
||||||
|
agentCreatorRelation.creator.id.eq(member.id)
|
||||||
|
.and(isActiveAssignment(currentTime))
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
member.role.eq(MemberRole.CREATOR)
|
||||||
|
.and(member.nickname.contains(searchWord))
|
||||||
|
)
|
||||||
|
.fetchOne()
|
||||||
|
?.toInt()
|
||||||
|
?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchAssignableCreators(
|
||||||
|
searchWord: String,
|
||||||
|
offset: Long,
|
||||||
|
limit: Long,
|
||||||
|
currentTime: LocalDateTime
|
||||||
|
): List<SearchAdminAgentAssignableCreatorItem> {
|
||||||
|
val currentAgent = QMember("currentAgent")
|
||||||
|
|
||||||
|
return queryFactory
|
||||||
|
.select(
|
||||||
|
QSearchAdminAgentAssignableCreatorItem(
|
||||||
|
member.id,
|
||||||
|
member.nickname,
|
||||||
|
currentAgent.id,
|
||||||
|
currentAgent.nickname
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.from(member)
|
||||||
|
.leftJoin(agentCreatorRelation)
|
||||||
|
.on(
|
||||||
|
agentCreatorRelation.creator.id.eq(member.id)
|
||||||
|
.and(isActiveAssignment(currentTime))
|
||||||
|
)
|
||||||
|
.leftJoin(agentCreatorRelation.agent, currentAgent)
|
||||||
|
.where(
|
||||||
|
member.role.eq(MemberRole.CREATOR)
|
||||||
|
.and(member.nickname.contains(searchWord))
|
||||||
|
)
|
||||||
|
.orderBy(member.id.desc())
|
||||||
|
.offset(offset)
|
||||||
|
.limit(limit)
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAssignedCreatorTotalCount(agentId: Long, currentTime: LocalDateTime): Int {
|
||||||
|
return queryFactory
|
||||||
|
.select(agentCreatorRelation.id.count())
|
||||||
|
.from(agentCreatorRelation)
|
||||||
|
.where(agentCreatorRelation.agent.id.eq(agentId).and(isActiveAssignment(currentTime)))
|
||||||
|
.fetchOne()
|
||||||
|
?.toInt()
|
||||||
|
?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAssignedCreators(
|
||||||
|
agentId: Long,
|
||||||
|
offset: Long,
|
||||||
|
limit: Long,
|
||||||
|
currentTime: LocalDateTime
|
||||||
|
): List<GetAdminAgentAssignedCreatorItem> {
|
||||||
|
return queryFactory
|
||||||
|
.select(
|
||||||
|
QGetAdminAgentAssignedCreatorItem(
|
||||||
|
agentCreatorRelation.creator.id,
|
||||||
|
agentCreatorRelation.creator.nickname,
|
||||||
|
agentCreatorRelation.assignedAt
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.from(agentCreatorRelation)
|
||||||
|
.where(agentCreatorRelation.agent.id.eq(agentId).and(isActiveAssignment(currentTime)))
|
||||||
|
.orderBy(agentCreatorRelation.creator.id.desc())
|
||||||
|
.offset(offset)
|
||||||
|
.limit(limit)
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isActiveAssignment(currentTime: LocalDateTime): BooleanExpression {
|
||||||
|
return agentCreatorRelation.assignedAt.loe(currentTime)
|
||||||
|
.and(agentCreatorRelation.unassignedAt.isNull.or(agentCreatorRelation.unassignedAt.gt(currentTime)))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package kr.co.vividnext.sodalive.admin.partner.agent.read
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateService
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.GetAgentChannelDonationSettlementByCreatorResponse
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.GetAgentSettlementByCreatorResponse
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class AdminAgentReadService(
|
||||||
|
private val queryRepository: AdminAgentReadQueryRepository,
|
||||||
|
private val memberRepository: MemberRepository,
|
||||||
|
private val calculateService: AgentCalculateService
|
||||||
|
) {
|
||||||
|
private val kstZoneId = ZoneId.of("Asia/Seoul")
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
fun getAgentList(offset: Long, limit: Long): GetAdminAgentListResponse {
|
||||||
|
val now = ZonedDateTime.now(kstZoneId)
|
||||||
|
return GetAdminAgentListResponse(
|
||||||
|
totalCount = queryRepository.getAgentListTotalCount(),
|
||||||
|
items = queryRepository.getAgentList(offset = offset, limit = limit, currentTime = now)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
fun searchAssignableCreators(searchWord: String, offset: Long, limit: Long): SearchAdminAgentAssignableCreatorResponse {
|
||||||
|
if (searchWord.length < 2) throw SodaException(messageKey = "admin.member.search_word_min_length")
|
||||||
|
val now = LocalDateTime.now()
|
||||||
|
return SearchAdminAgentAssignableCreatorResponse(
|
||||||
|
totalCount = queryRepository.searchAssignableCreatorsTotalCount(searchWord = searchWord, currentTime = now),
|
||||||
|
items = queryRepository.searchAssignableCreators(
|
||||||
|
searchWord = searchWord,
|
||||||
|
offset = offset,
|
||||||
|
limit = limit,
|
||||||
|
currentTime = now
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
fun getAssignedCreators(agentId: Long, offset: Long, limit: Long): GetAdminAgentAssignedCreatorResponse {
|
||||||
|
validateAgent(agentId)
|
||||||
|
val now = LocalDateTime.now()
|
||||||
|
return GetAdminAgentAssignedCreatorResponse(
|
||||||
|
totalCount = queryRepository.getAssignedCreatorTotalCount(agentId = agentId, currentTime = now),
|
||||||
|
items = queryRepository.getAssignedCreators(agentId = agentId, offset = offset, limit = limit, currentTime = now)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
fun getCalculateLiveByCreator(
|
||||||
|
agentId: Long,
|
||||||
|
startDateStr: String,
|
||||||
|
endDateStr: String,
|
||||||
|
offset: Long,
|
||||||
|
limit: Long
|
||||||
|
): GetAgentSettlementByCreatorResponse {
|
||||||
|
validateAgent(agentId)
|
||||||
|
return calculateService.getCalculateLiveByCreator(startDateStr, endDateStr, agentId, offset, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
fun getCalculateContentByCreator(
|
||||||
|
agentId: Long,
|
||||||
|
startDateStr: String,
|
||||||
|
endDateStr: String,
|
||||||
|
offset: Long,
|
||||||
|
limit: Long
|
||||||
|
): GetAgentSettlementByCreatorResponse {
|
||||||
|
validateAgent(agentId)
|
||||||
|
return calculateService.getCalculateContentByCreator(startDateStr, endDateStr, agentId, offset, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
fun getCalculateCommunityByCreator(
|
||||||
|
agentId: Long,
|
||||||
|
startDateStr: String,
|
||||||
|
endDateStr: String,
|
||||||
|
offset: Long,
|
||||||
|
limit: Long
|
||||||
|
): GetAgentSettlementByCreatorResponse {
|
||||||
|
validateAgent(agentId)
|
||||||
|
return calculateService.getCalculateCommunityByCreator(startDateStr, endDateStr, agentId, offset, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
fun getCalculateContentDonationByCreator(
|
||||||
|
agentId: Long,
|
||||||
|
startDateStr: String,
|
||||||
|
endDateStr: String,
|
||||||
|
offset: Long,
|
||||||
|
limit: Long
|
||||||
|
): GetAgentSettlementByCreatorResponse {
|
||||||
|
validateAgent(agentId)
|
||||||
|
return calculateService.getCalculateContentDonationByCreator(startDateStr, endDateStr, agentId, offset, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
fun getChannelDonationByCreator(
|
||||||
|
agentId: Long,
|
||||||
|
startDateStr: String,
|
||||||
|
endDateStr: String,
|
||||||
|
offset: Long,
|
||||||
|
limit: Long
|
||||||
|
): GetAgentChannelDonationSettlementByCreatorResponse {
|
||||||
|
validateAgent(agentId)
|
||||||
|
return calculateService.getChannelDonationByCreator(startDateStr, endDateStr, agentId, offset, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateAgent(agentId: Long) {
|
||||||
|
val member = memberRepository.findById(agentId).orElseThrow {
|
||||||
|
SodaException(messageKey = "partner.agent.ratio.agent_not_found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (member.role != MemberRole.AGENT) {
|
||||||
|
throw SodaException(messageKey = "partner.agent.ratio.invalid_agent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package kr.co.vividnext.sodalive.admin.partner.agent.read
|
||||||
|
|
||||||
|
import com.querydsl.core.annotations.QueryProjection
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
data class GetAdminAgentAssignedCreatorResponse(
|
||||||
|
val totalCount: Int,
|
||||||
|
val items: List<GetAdminAgentAssignedCreatorItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class GetAdminAgentAssignedCreatorItem @QueryProjection constructor(
|
||||||
|
val creatorId: Long,
|
||||||
|
val creatorNickname: String,
|
||||||
|
val assignedAt: LocalDateTime
|
||||||
|
)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package kr.co.vividnext.sodalive.admin.partner.agent.read
|
||||||
|
|
||||||
|
import com.querydsl.core.annotations.QueryProjection
|
||||||
|
|
||||||
|
data class GetAdminAgentListResponse(
|
||||||
|
val totalCount: Int,
|
||||||
|
val items: List<GetAdminAgentListItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class GetAdminAgentListItem @QueryProjection constructor(
|
||||||
|
val agentId: Long,
|
||||||
|
val agentNickname: String,
|
||||||
|
val assignedCreatorCount: Int,
|
||||||
|
val liveAgentSettlementAmount: Int = 0,
|
||||||
|
val contentAgentSettlementAmount: Int = 0,
|
||||||
|
val communityAgentSettlementAmount: Int = 0,
|
||||||
|
val contentDonationAgentSettlementAmount: Int = 0,
|
||||||
|
val channelDonationAgentSettlementAmount: Int = 0
|
||||||
|
)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package kr.co.vividnext.sodalive.admin.partner.agent.read
|
||||||
|
|
||||||
|
import com.querydsl.core.annotations.QueryProjection
|
||||||
|
|
||||||
|
data class SearchAdminAgentAssignableCreatorResponse(
|
||||||
|
val totalCount: Int,
|
||||||
|
val items: List<SearchAdminAgentAssignableCreatorItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SearchAdminAgentAssignableCreatorItem @QueryProjection constructor(
|
||||||
|
val creatorId: Long,
|
||||||
|
val creatorNickname: String,
|
||||||
|
val currentAgentId: Long?,
|
||||||
|
val currentAgentNickname: String?
|
||||||
|
)
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package kr.co.vividnext.sodalive.admin.partner.agent.read
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.CountryContext
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
|
||||||
|
import org.springframework.boot.test.context.TestConfiguration
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Import
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
|
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous
|
||||||
|
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
|
||||||
|
import org.springframework.security.web.SecurityFilterChain
|
||||||
|
import org.springframework.security.web.authentication.HttpStatusEntryPoint
|
||||||
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
||||||
|
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
|
||||||
|
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||||
|
import javax.servlet.http.HttpServletResponse
|
||||||
|
|
||||||
|
@WebMvcTest(AdminAgentReadController::class)
|
||||||
|
@Import(AdminAgentReadControllerSecurityTest.TestSecurityConfig::class)
|
||||||
|
class AdminAgentReadControllerSecurityTest @Autowired constructor(
|
||||||
|
private val mockMvc: MockMvc
|
||||||
|
) {
|
||||||
|
@MockBean
|
||||||
|
private lateinit var service: AdminAgentReadService
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private lateinit var countryContext: CountryContext
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private lateinit var langContext: LangContext
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private lateinit var sodaMessageSource: SodaMessageSource
|
||||||
|
|
||||||
|
@TestConfiguration
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
|
class TestSecurityConfig {
|
||||||
|
@Bean
|
||||||
|
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
return http
|
||||||
|
.csrf().disable()
|
||||||
|
.authorizeRequests()
|
||||||
|
.antMatchers("/admin/partner/agent/**").hasRole("ADMIN")
|
||||||
|
.anyRequest().permitAll()
|
||||||
|
.and()
|
||||||
|
.exceptionHandling()
|
||||||
|
.authenticationEntryPoint(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
|
||||||
|
.accessDeniedHandler { _, response, _ -> response.sendError(HttpServletResponse.SC_FORBIDDEN) }
|
||||||
|
.and()
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("관리자 권한이면 에이전트 목록 조회에 성공한다")
|
||||||
|
fun shouldAllowAdminRole() {
|
||||||
|
Mockito.`when`(service.getAgentList(offset = 0L, limit = 20L)).thenReturn(
|
||||||
|
GetAdminAgentListResponse(
|
||||||
|
totalCount = 1,
|
||||||
|
items = listOf(
|
||||||
|
GetAdminAgentListItem(
|
||||||
|
agentId = 11L,
|
||||||
|
agentNickname = "agent-a",
|
||||||
|
assignedCreatorCount = 2,
|
||||||
|
liveAgentSettlementAmount = 131,
|
||||||
|
contentAgentSettlementAmount = 80,
|
||||||
|
communityAgentSettlementAmount = 55,
|
||||||
|
contentDonationAgentSettlementAmount = 12,
|
||||||
|
channelDonationAgentSettlementAmount = 397
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = mockMvc.perform(
|
||||||
|
get("/admin/partner/agent/list")
|
||||||
|
.param("page", "0")
|
||||||
|
.param("size", "20")
|
||||||
|
.with(user("admin").roles("ADMIN"))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.success").value(true))
|
||||||
|
.andReturn()
|
||||||
|
|
||||||
|
println(result.response.contentAsString)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("익명 사용자는 관리자 조회 API에 접근할 수 없다")
|
||||||
|
fun shouldRejectAnonymousUser() {
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/admin/partner/agent/list")
|
||||||
|
.param("page", "0")
|
||||||
|
.param("size", "20")
|
||||||
|
.with(anonymous())
|
||||||
|
)
|
||||||
|
.andExpect(status().isUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("에이전트 권한 사용자는 관리자 조회 API에 접근할 수 없다")
|
||||||
|
fun shouldRejectAgentRole() {
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/admin/partner/agent/list")
|
||||||
|
.param("page", "0")
|
||||||
|
.param("size", "20")
|
||||||
|
.with(user("agent").roles("AGENT"))
|
||||||
|
)
|
||||||
|
.andExpect(status().isForbidden)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("일반 사용자 권한 사용자는 관리자 조회 API에 접근할 수 없다")
|
||||||
|
fun shouldRejectUserRole() {
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/admin/partner/agent/list")
|
||||||
|
.param("page", "0")
|
||||||
|
.param("size", "20")
|
||||||
|
.with(user("user").roles("USER"))
|
||||||
|
)
|
||||||
|
.andExpect(status().isForbidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
package kr.co.vividnext.sodalive.admin.partner.agent.read
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.GetAgentChannelDonationSettlementByCreatorItem
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.GetAgentChannelDonationSettlementByCreatorResponse
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.GetAgentChannelDonationSettlementTotal
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.GetAgentSettlementByCreatorItem
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.GetAgentSettlementByCreatorResponse
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.GetAgentSettlementByCreatorTotal
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.springframework.data.domain.PageRequest
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
class AdminAgentReadControllerTest {
|
||||||
|
private lateinit var service: AdminAgentReadService
|
||||||
|
private lateinit var controller: AdminAgentReadController
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
service = Mockito.mock(AdminAgentReadService::class.java)
|
||||||
|
controller = AdminAgentReadController(service)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("관리자 컨트롤러는 에이전트 목록 조회 파라미터를 서비스로 전달한다")
|
||||||
|
fun shouldForwardAgentListRequest() {
|
||||||
|
val body = GetAdminAgentListResponse(
|
||||||
|
totalCount = 1,
|
||||||
|
items = listOf(
|
||||||
|
GetAdminAgentListItem(
|
||||||
|
agentId = 11L,
|
||||||
|
agentNickname = "agent-a",
|
||||||
|
assignedCreatorCount = 2,
|
||||||
|
liveAgentSettlementAmount = 131,
|
||||||
|
contentAgentSettlementAmount = 80,
|
||||||
|
communityAgentSettlementAmount = 55,
|
||||||
|
contentDonationAgentSettlementAmount = 12,
|
||||||
|
channelDonationAgentSettlementAmount = 397
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Mockito.`when`(service.getAgentList(offset = 20L, limit = 20L)).thenReturn(body)
|
||||||
|
|
||||||
|
val response = controller.getAgentList(PageRequest.of(1, 20))
|
||||||
|
|
||||||
|
assertEquals(true, response.success)
|
||||||
|
assertEquals(2, response.data!!.items.first().assignedCreatorCount)
|
||||||
|
assertEquals(131, response.data!!.items.first().liveAgentSettlementAmount)
|
||||||
|
assertEquals(397, response.data!!.items.first().channelDonationAgentSettlementAmount)
|
||||||
|
Mockito.verify(service).getAgentList(offset = 20L, limit = 20L)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("관리자 컨트롤러는 크리에이터 검색 파라미터를 서비스로 전달한다")
|
||||||
|
fun shouldForwardCreatorSearchRequest() {
|
||||||
|
val body = SearchAdminAgentAssignableCreatorResponse(
|
||||||
|
totalCount = 1,
|
||||||
|
items = listOf(
|
||||||
|
SearchAdminAgentAssignableCreatorItem(
|
||||||
|
creatorId = 21L,
|
||||||
|
creatorNickname = "creator-a",
|
||||||
|
currentAgentId = 11L,
|
||||||
|
currentAgentNickname = "agent-a"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Mockito.`when`(service.searchAssignableCreators(searchWord = "creator", offset = 0L, limit = 10L)).thenReturn(body)
|
||||||
|
|
||||||
|
val response = controller.searchAssignableCreators(searchWord = "creator", pageable = PageRequest.of(0, 10))
|
||||||
|
|
||||||
|
assertEquals(true, response.success)
|
||||||
|
assertEquals(11L, response.data!!.items.first().currentAgentId)
|
||||||
|
Mockito.verify(service).searchAssignableCreators(searchWord = "creator", offset = 0L, limit = 10L)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("관리자 컨트롤러는 소속 크리에이터 목록 조회 파라미터를 서비스로 전달한다")
|
||||||
|
fun shouldForwardAssignedCreatorListRequest() {
|
||||||
|
val body = GetAdminAgentAssignedCreatorResponse(
|
||||||
|
totalCount = 1,
|
||||||
|
items = listOf(
|
||||||
|
GetAdminAgentAssignedCreatorItem(
|
||||||
|
creatorId = 21L,
|
||||||
|
creatorNickname = "creator-a",
|
||||||
|
assignedAt = LocalDateTime.of(2026, 4, 1, 10, 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Mockito.`when`(service.getAssignedCreators(agentId = 11L, offset = 10L, limit = 5L)).thenReturn(body)
|
||||||
|
|
||||||
|
val response = controller.getAssignedCreators(agentId = 11L, pageable = PageRequest.of(2, 5))
|
||||||
|
|
||||||
|
assertEquals(true, response.success)
|
||||||
|
assertEquals(21L, response.data!!.items.first().creatorId)
|
||||||
|
Mockito.verify(service).getAssignedCreators(agentId = 11L, offset = 10L, limit = 5L)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("관리자 컨트롤러는 라이브 정산 조회 파라미터를 서비스로 전달한다")
|
||||||
|
fun shouldForwardLiveSettlementRequest() {
|
||||||
|
val body = GetAgentSettlementByCreatorResponse(
|
||||||
|
totalCount = 1,
|
||||||
|
total = GetAgentSettlementByCreatorTotal(1, 50, 5000, 330, 3736, 123, 3613, 131),
|
||||||
|
items = listOf(GetAgentSettlementByCreatorItem(21L, "creator-a", 1, 50, 5000, 330, 3736, 123, 3613, 131))
|
||||||
|
)
|
||||||
|
Mockito.`when`(
|
||||||
|
service.getCalculateLiveByCreator(
|
||||||
|
agentId = 11L,
|
||||||
|
startDateStr = "2026-04-01",
|
||||||
|
endDateStr = "2026-04-30",
|
||||||
|
offset = 0L,
|
||||||
|
limit = 20L
|
||||||
|
)
|
||||||
|
).thenReturn(body)
|
||||||
|
|
||||||
|
val response = controller.getCalculateLiveByCreator(
|
||||||
|
agentId = 11L,
|
||||||
|
startDateStr = "2026-04-01",
|
||||||
|
endDateStr = "2026-04-30",
|
||||||
|
pageable = PageRequest.of(0, 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(true, response.success)
|
||||||
|
assertEquals(131, response.data!!.items.first().agentSettlementAmount)
|
||||||
|
Mockito.verify(service).getCalculateLiveByCreator(11L, "2026-04-01", "2026-04-30", 0L, 20L)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("관리자 컨트롤러는 채널후원 정산 조회 파라미터를 서비스로 전달한다")
|
||||||
|
fun shouldForwardChannelDonationSettlementRequest() {
|
||||||
|
val body = GetAgentChannelDonationSettlementByCreatorResponse(
|
||||||
|
totalCount = 1,
|
||||||
|
total = GetAgentChannelDonationSettlementTotal(1, 50, 5000, 330, 3970, 131, 3839, 397),
|
||||||
|
items = listOf(
|
||||||
|
GetAgentChannelDonationSettlementByCreatorItem(
|
||||||
|
21L,
|
||||||
|
"creator-a",
|
||||||
|
1,
|
||||||
|
50,
|
||||||
|
5000,
|
||||||
|
330,
|
||||||
|
3970,
|
||||||
|
131,
|
||||||
|
3839,
|
||||||
|
397
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Mockito.`when`(
|
||||||
|
service.getChannelDonationByCreator(
|
||||||
|
agentId = 11L,
|
||||||
|
startDateStr = "2026-04-01",
|
||||||
|
endDateStr = "2026-04-30",
|
||||||
|
offset = 0L,
|
||||||
|
limit = 20L
|
||||||
|
)
|
||||||
|
).thenReturn(body)
|
||||||
|
|
||||||
|
val response = controller.getChannelDonationByCreator(
|
||||||
|
agentId = 11L,
|
||||||
|
startDateStr = "2026-04-01",
|
||||||
|
endDateStr = "2026-04-30",
|
||||||
|
pageable = PageRequest.of(0, 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(true, response.success)
|
||||||
|
assertEquals(397, response.data!!.items.first().agentSettlementAmount)
|
||||||
|
Mockito.verify(service).getChannelDonationByCreator(11L, "2026-04-01", "2026-04-30", 0L, 20L)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,382 @@
|
|||||||
|
package kr.co.vividnext.sodalive.admin.partner.agent.read
|
||||||
|
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.admin.calculate.ratio.CreatorSettlementRatio
|
||||||
|
import kr.co.vividnext.sodalive.admin.calculate.ratio.CreatorSettlementRatioRepository
|
||||||
|
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||||
|
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||||
|
import kr.co.vividnext.sodalive.can.use.UseCan
|
||||||
|
import kr.co.vividnext.sodalive.can.use.UseCanCalculate
|
||||||
|
import kr.co.vividnext.sodalive.can.use.UseCanCalculateRepository
|
||||||
|
import kr.co.vividnext.sodalive.can.use.UseCanCalculateStatus
|
||||||
|
import kr.co.vividnext.sodalive.can.use.UseCanRepository
|
||||||
|
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||||
|
import kr.co.vividnext.sodalive.content.AudioContent
|
||||||
|
import kr.co.vividnext.sodalive.content.AudioContentRepository
|
||||||
|
import kr.co.vividnext.sodalive.content.order.Order
|
||||||
|
import kr.co.vividnext.sodalive.content.order.OrderRepository
|
||||||
|
import kr.co.vividnext.sodalive.content.order.OrderType
|
||||||
|
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunityRepository
|
||||||
|
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||||
|
import kr.co.vividnext.sodalive.live.room.LiveRoomRepository
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.assignment.AgentCreatorRelation
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.assignment.AgentCreatorRelationRepository
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepository
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateService
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.settlement.snapshot.AgentSettlementSnapshotRepository
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
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 java.time.LocalDateTime
|
||||||
|
import java.time.Month
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import javax.persistence.EntityManager
|
||||||
|
|
||||||
|
@DataJpaTest(properties = ["spring.jpa.database-platform=kr.co.vividnext.sodalive.support.H2MySqlFunctionDialect"])
|
||||||
|
@Import(QueryDslConfig::class)
|
||||||
|
class AdminAgentReadCurrentMonthListSummaryTest @Autowired constructor(
|
||||||
|
private val queryFactory: JPAQueryFactory,
|
||||||
|
private val memberRepository: MemberRepository,
|
||||||
|
private val relationRepository: AgentCreatorRelationRepository,
|
||||||
|
private val creatorSettlementRatioRepository: CreatorSettlementRatioRepository,
|
||||||
|
private val audioContentRepository: AudioContentRepository,
|
||||||
|
private val orderRepository: OrderRepository,
|
||||||
|
private val liveRoomRepository: LiveRoomRepository,
|
||||||
|
private val creatorCommunityRepository: CreatorCommunityRepository,
|
||||||
|
private val useCanRepository: UseCanRepository,
|
||||||
|
private val useCanCalculateRepository: UseCanCalculateRepository,
|
||||||
|
private val snapshotRepository: AgentSettlementSnapshotRepository,
|
||||||
|
private val entityManager: EntityManager
|
||||||
|
) {
|
||||||
|
private lateinit var repository: AdminAgentReadQueryRepository
|
||||||
|
private lateinit var calculateService: AgentCalculateService
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
val calculateQueryRepository = AgentCalculateQueryRepository(queryFactory, entityManager)
|
||||||
|
repository = AdminAgentReadQueryRepository(queryFactory, calculateQueryRepository)
|
||||||
|
calculateService = AgentCalculateService(calculateQueryRepository, snapshotRepository)
|
||||||
|
registerMysqlDateFunctions()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("에이전트 목록 조회는 현재 월 5종 정산 합계를 포함하고 내역이 없으면 0을 반환한다")
|
||||||
|
fun shouldReturnCurrentMonthSettlementSummariesAndZeroForEmptyAgents() {
|
||||||
|
val now = ZonedDateTime.of(LocalDateTime.of(2026, 4, 10, 12, 0), ZoneId.of("Asia/Seoul"))
|
||||||
|
val agentWithRows = saveMember("agent-with-rows", MemberRole.AGENT)
|
||||||
|
val emptyAgent = saveMember("agent-empty", MemberRole.AGENT)
|
||||||
|
seedCurrentMonthSettlementFixtures(agentWithRows, now.toLocalDateTime())
|
||||||
|
|
||||||
|
val items = repository.getAgentList(offset = 0, limit = 20, currentTime = now)
|
||||||
|
val emptyItem = items.first { it.agentId == emptyAgent.id }
|
||||||
|
val agentItem = items.first { it.agentId == agentWithRows.id }
|
||||||
|
|
||||||
|
assertEquals(0, emptyItem.liveAgentSettlementAmount)
|
||||||
|
assertEquals(0, emptyItem.contentAgentSettlementAmount)
|
||||||
|
assertEquals(0, emptyItem.communityAgentSettlementAmount)
|
||||||
|
assertEquals(0, emptyItem.contentDonationAgentSettlementAmount)
|
||||||
|
assertEquals(0, emptyItem.channelDonationAgentSettlementAmount)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
calculateService
|
||||||
|
.getCalculateLiveByCreator("2026-04-01", "2026-04-30", agentWithRows.id!!, 0L, 20L)
|
||||||
|
.total.agentSettlementAmount,
|
||||||
|
agentItem.liveAgentSettlementAmount
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
calculateService
|
||||||
|
.getCalculateContentByCreator("2026-04-01", "2026-04-30", agentWithRows.id!!, 0L, 20L)
|
||||||
|
.total.agentSettlementAmount,
|
||||||
|
agentItem.contentAgentSettlementAmount
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
calculateService
|
||||||
|
.getCalculateCommunityByCreator("2026-04-01", "2026-04-30", agentWithRows.id!!, 0L, 20L)
|
||||||
|
.total.agentSettlementAmount,
|
||||||
|
agentItem.communityAgentSettlementAmount
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
calculateService
|
||||||
|
.getCalculateContentDonationByCreator("2026-04-01", "2026-04-30", agentWithRows.id!!, 0L, 20L)
|
||||||
|
.total.agentSettlementAmount,
|
||||||
|
agentItem.contentDonationAgentSettlementAmount
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
calculateService
|
||||||
|
.getChannelDonationByCreator("2026-04-01", "2026-04-30", agentWithRows.id!!, 0L, 20L)
|
||||||
|
.total.agentSettlementAmount,
|
||||||
|
agentItem.channelDonationAgentSettlementAmount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("에이전트 목록 현재 월 경계는 Asia/Seoul 기준으로 계산한다")
|
||||||
|
fun shouldCalculateCurrentMonthBoundaryInAsiaSeoul() {
|
||||||
|
val currentTime = ZonedDateTime.of(
|
||||||
|
LocalDateTime.of(2026, 3, 31, 15, 30),
|
||||||
|
ZoneId.of("UTC")
|
||||||
|
)
|
||||||
|
val agent = saveMember("agent-kst-boundary", MemberRole.AGENT)
|
||||||
|
|
||||||
|
seedUtcBoundaryFixtures(agent)
|
||||||
|
|
||||||
|
val items = repository.getAgentList(offset = 0, limit = 20, currentTime = currentTime)
|
||||||
|
val item = items.first { it.agentId == agent.id }
|
||||||
|
|
||||||
|
assertEquals(true, item.liveAgentSettlementAmount > 0)
|
||||||
|
assertEquals(true, item.contentAgentSettlementAmount > 0)
|
||||||
|
assertEquals(true, item.communityAgentSettlementAmount > 0)
|
||||||
|
assertEquals(true, item.contentDonationAgentSettlementAmount > 0)
|
||||||
|
assertEquals(true, item.channelDonationAgentSettlementAmount > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("에이전트 목록 현재 월 경계는 UTC 조회 기준으로는 전월 말 15시부터 시작될 수 있다")
|
||||||
|
fun shouldIncludeUtcRowsThatBelongToKstMonthStart() {
|
||||||
|
val currentTime = ZonedDateTime.of(
|
||||||
|
LocalDateTime.of(2026, 3, 31, 15, 30),
|
||||||
|
ZoneId.of("UTC")
|
||||||
|
)
|
||||||
|
val agent = saveMember("agent-kst-utc-range", MemberRole.AGENT)
|
||||||
|
|
||||||
|
seedUtcBoundaryFixtures(agent)
|
||||||
|
|
||||||
|
val items = repository.getAgentList(offset = 0, limit = 20, currentTime = currentTime)
|
||||||
|
val item = items.first { it.agentId == agent.id }
|
||||||
|
|
||||||
|
assertEquals(Month.APRIL, currentTime.withZoneSameInstant(ZoneId.of("Asia/Seoul")).month)
|
||||||
|
assertEquals(true, item.liveAgentSettlementAmount > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun seedCurrentMonthSettlementFixtures(agent: Member, now: LocalDateTime) {
|
||||||
|
val creator = saveMember("creator-${agent.nickname}", MemberRole.CREATOR)
|
||||||
|
val buyer = saveMember("buyer-${agent.nickname}", MemberRole.USER)
|
||||||
|
val sender = saveMember("sender-${agent.nickname}", MemberRole.USER)
|
||||||
|
|
||||||
|
saveRelation(agent, creator, assignedAt = now.withDayOfMonth(1).minusDays(1))
|
||||||
|
saveCreatorSettlementRatio(creator, live = 70, content = 60, community = 65)
|
||||||
|
|
||||||
|
val liveRoom = saveLiveRoom(creator, now.withDayOfMonth(5).withHour(8))
|
||||||
|
saveLiveUseCan(sender, liveRoom, 10, now.withDayOfMonth(5).withHour(9))
|
||||||
|
saveLiveUseCan(sender, liveRoom, 20, now.withDayOfMonth(5).withHour(10))
|
||||||
|
|
||||||
|
val paidContent = saveAudioContent(creator, "content-${agent.nickname}", price = 50, settlementRatio = 80)
|
||||||
|
val donationContent = saveAudioContent(creator, "donation-${agent.nickname}", price = 0, settlementRatio = null)
|
||||||
|
saveOrder(buyer, creator, paidContent, now.withDayOfMonth(6).withHour(11))
|
||||||
|
|
||||||
|
val communityPost = saveCommunityPost(creator, 10)
|
||||||
|
saveCommunityUseCan(buyer, communityPost, 15, now.withDayOfMonth(7).withHour(12))
|
||||||
|
|
||||||
|
saveContentDonationUseCan(buyer, donationContent, 12, now.withDayOfMonth(8).withHour(13))
|
||||||
|
|
||||||
|
val channelDonation = saveChannelDonationUseCan(sender, 40, now.withDayOfMonth(9).withHour(14))
|
||||||
|
saveUseCanCalculate(channelDonation, creator.id!!, 15, PaymentGateway.PG)
|
||||||
|
saveUseCanCalculate(channelDonation, creator.id!!, 25, PaymentGateway.GOOGLE_IAP)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun seedUtcBoundaryFixtures(agent: Member) {
|
||||||
|
val creator = saveMember("creator-${agent.nickname}", MemberRole.CREATOR)
|
||||||
|
val buyer = saveMember("buyer-${agent.nickname}", MemberRole.USER)
|
||||||
|
val sender = saveMember("sender-${agent.nickname}", MemberRole.USER)
|
||||||
|
|
||||||
|
saveRelation(agent, creator, assignedAt = LocalDateTime.of(2026, 3, 1, 0, 0))
|
||||||
|
saveCreatorSettlementRatio(creator, live = 70, content = 60, community = 65)
|
||||||
|
|
||||||
|
val liveRoom = saveLiveRoom(creator, LocalDateTime.of(2026, 3, 31, 15, 10))
|
||||||
|
saveLiveUseCan(sender, liveRoom, 10, LocalDateTime.of(2026, 3, 31, 15, 30))
|
||||||
|
saveLiveUseCan(sender, liveRoom, 20, LocalDateTime.of(2026, 3, 31, 15, 40))
|
||||||
|
|
||||||
|
val paidContent = saveAudioContent(creator, "content-${agent.nickname}", price = 50, settlementRatio = 80)
|
||||||
|
val donationContent = saveAudioContent(creator, "donation-${agent.nickname}", price = 0, settlementRatio = null)
|
||||||
|
saveOrder(buyer, creator, paidContent, LocalDateTime.of(2026, 3, 31, 15, 50))
|
||||||
|
|
||||||
|
val communityPost = saveCommunityPost(creator, 10)
|
||||||
|
saveCommunityUseCan(buyer, communityPost, 15, LocalDateTime.of(2026, 3, 31, 15, 55))
|
||||||
|
|
||||||
|
saveContentDonationUseCan(buyer, donationContent, 12, LocalDateTime.of(2026, 3, 31, 15, 58))
|
||||||
|
|
||||||
|
val channelDonation = saveChannelDonationUseCan(sender, 40, LocalDateTime.of(2026, 3, 31, 15, 59))
|
||||||
|
saveUseCanCalculate(channelDonation, creator.id!!, 15, PaymentGateway.PG)
|
||||||
|
saveUseCanCalculate(channelDonation, creator.id!!, 25, PaymentGateway.GOOGLE_IAP)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveMember(nickname: String, role: MemberRole): Member {
|
||||||
|
return memberRepository.saveAndFlush(
|
||||||
|
Member(
|
||||||
|
email = "$nickname@test.com",
|
||||||
|
password = "password",
|
||||||
|
nickname = nickname,
|
||||||
|
role = role
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveRelation(agent: Member, creator: Member, assignedAt: LocalDateTime) {
|
||||||
|
val relation = AgentCreatorRelation()
|
||||||
|
relation.agent = agent
|
||||||
|
relation.creator = creator
|
||||||
|
relation.assignedAt = assignedAt
|
||||||
|
relation.unassignedAt = null
|
||||||
|
relationRepository.saveAndFlush(relation)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveCreatorSettlementRatio(creator: Member, live: Int, content: Int, community: Int) {
|
||||||
|
val ratio = CreatorSettlementRatio(
|
||||||
|
subsidy = 0,
|
||||||
|
liveSettlementRatio = live,
|
||||||
|
contentSettlementRatio = content,
|
||||||
|
communitySettlementRatio = community
|
||||||
|
)
|
||||||
|
ratio.member = creator
|
||||||
|
creatorSettlementRatioRepository.saveAndFlush(ratio)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveLiveRoom(creator: Member, beginDateTime: LocalDateTime): LiveRoom {
|
||||||
|
val room = LiveRoom(
|
||||||
|
title = "live-room",
|
||||||
|
notice = "notice",
|
||||||
|
beginDateTime = beginDateTime,
|
||||||
|
numberOfPeople = 10,
|
||||||
|
isAdult = false,
|
||||||
|
price = 10
|
||||||
|
)
|
||||||
|
room.member = creator
|
||||||
|
return liveRoomRepository.saveAndFlush(room)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveLiveUseCan(sender: Member, room: LiveRoom, can: Int, createdAt: LocalDateTime): UseCan {
|
||||||
|
val useCan = UseCan(
|
||||||
|
canUsage = CanUsage.LIVE,
|
||||||
|
can = can,
|
||||||
|
rewardCan = 0
|
||||||
|
)
|
||||||
|
useCan.member = sender
|
||||||
|
useCan.room = room
|
||||||
|
val saved = useCanRepository.saveAndFlush(useCan)
|
||||||
|
updateUseCanCreatedAt(saved.id!!, createdAt)
|
||||||
|
return saved
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveAudioContent(creator: Member, title: String, price: Int, settlementRatio: Int?): AudioContent {
|
||||||
|
val theme = AudioContentTheme(
|
||||||
|
theme = "theme-$title",
|
||||||
|
image = "image-$title.png"
|
||||||
|
)
|
||||||
|
entityManager.persist(theme)
|
||||||
|
|
||||||
|
val audioContent = AudioContent(
|
||||||
|
title = title,
|
||||||
|
detail = "detail-$title",
|
||||||
|
languageCode = "ko",
|
||||||
|
price = price,
|
||||||
|
settlementRatio = settlementRatio
|
||||||
|
)
|
||||||
|
audioContent.theme = theme
|
||||||
|
audioContent.member = creator
|
||||||
|
audioContent.isActive = true
|
||||||
|
return audioContentRepository.saveAndFlush(audioContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveOrder(buyer: Member, creator: Member, content: AudioContent, createdAt: LocalDateTime): Order {
|
||||||
|
val order = Order(type = OrderType.KEEP)
|
||||||
|
order.member = buyer
|
||||||
|
order.creator = creator
|
||||||
|
order.audioContent = content
|
||||||
|
val saved = orderRepository.saveAndFlush(order)
|
||||||
|
updateOrderCreatedAt(saved.id!!, createdAt)
|
||||||
|
return saved
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveCommunityPost(creator: Member, price: Int): CreatorCommunity {
|
||||||
|
val post = CreatorCommunity(
|
||||||
|
content = "community-content-$price",
|
||||||
|
price = price,
|
||||||
|
isCommentAvailable = true,
|
||||||
|
isAdult = false
|
||||||
|
)
|
||||||
|
post.member = creator
|
||||||
|
return creatorCommunityRepository.saveAndFlush(post)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveCommunityUseCan(buyer: Member, post: CreatorCommunity, can: Int, createdAt: LocalDateTime): UseCan {
|
||||||
|
val useCan = UseCan(
|
||||||
|
canUsage = CanUsage.PAID_COMMUNITY_POST,
|
||||||
|
can = can,
|
||||||
|
rewardCan = 0
|
||||||
|
)
|
||||||
|
useCan.member = buyer
|
||||||
|
useCan.communityPost = post
|
||||||
|
val saved = useCanRepository.saveAndFlush(useCan)
|
||||||
|
updateUseCanCreatedAt(saved.id!!, createdAt)
|
||||||
|
return saved
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveContentDonationUseCan(buyer: Member, content: AudioContent, can: Int, createdAt: LocalDateTime): UseCan {
|
||||||
|
val useCan = UseCan(
|
||||||
|
canUsage = CanUsage.DONATION,
|
||||||
|
can = can,
|
||||||
|
rewardCan = 0
|
||||||
|
)
|
||||||
|
useCan.member = buyer
|
||||||
|
useCan.audioContent = content
|
||||||
|
val saved = useCanRepository.saveAndFlush(useCan)
|
||||||
|
updateUseCanCreatedAt(saved.id!!, createdAt)
|
||||||
|
return saved
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveChannelDonationUseCan(sender: Member, can: Int, createdAt: LocalDateTime): UseCan {
|
||||||
|
val useCan = UseCan(
|
||||||
|
canUsage = CanUsage.CHANNEL_DONATION,
|
||||||
|
can = can,
|
||||||
|
rewardCan = 0
|
||||||
|
)
|
||||||
|
useCan.member = sender
|
||||||
|
val saved = useCanRepository.saveAndFlush(useCan)
|
||||||
|
updateUseCanCreatedAt(saved.id!!, createdAt)
|
||||||
|
return saved
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveUseCanCalculate(useCan: UseCan, recipientCreatorId: Long, can: Int, paymentGateway: PaymentGateway) {
|
||||||
|
val useCanCalculate = UseCanCalculate(
|
||||||
|
can = can,
|
||||||
|
paymentGateway = paymentGateway,
|
||||||
|
status = UseCanCalculateStatus.RECEIVED
|
||||||
|
)
|
||||||
|
useCanCalculate.useCan = useCan
|
||||||
|
useCanCalculate.recipientCreatorId = recipientCreatorId
|
||||||
|
useCanCalculateRepository.saveAndFlush(useCanCalculate)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUseCanCreatedAt(useCanId: Long, createdAt: LocalDateTime) {
|
||||||
|
entityManager.createQuery("update UseCan u set u.createdAt = :createdAt where u.id = :id")
|
||||||
|
.setParameter("createdAt", createdAt)
|
||||||
|
.setParameter("id", useCanId)
|
||||||
|
.executeUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateOrderCreatedAt(orderId: Long, createdAt: LocalDateTime) {
|
||||||
|
entityManager.createQuery("update Order o set o.createdAt = :createdAt where o.id = :id")
|
||||||
|
.setParameter("createdAt", createdAt)
|
||||||
|
.setParameter("id", orderId)
|
||||||
|
.executeUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerMysqlDateFunctions() {
|
||||||
|
entityManager.createNativeQuery(
|
||||||
|
"CREATE ALIAS IF NOT EXISTS DATE_FORMAT FOR 'kr.co.vividnext.sodalive.support.H2MysqlDateFunctions.dateFormat'"
|
||||||
|
).executeUpdate()
|
||||||
|
entityManager.createNativeQuery(
|
||||||
|
"CREATE ALIAS IF NOT EXISTS CONVERT_TZ FOR 'kr.co.vividnext.sodalive.support.H2MysqlDateFunctions.convertTz'"
|
||||||
|
).executeUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
package kr.co.vividnext.sodalive.admin.partner.agent.read
|
||||||
|
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.admin.calculate.ratio.CreatorSettlementRatio
|
||||||
|
import kr.co.vividnext.sodalive.admin.calculate.ratio.CreatorSettlementRatioRepository
|
||||||
|
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||||
|
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||||
|
import kr.co.vividnext.sodalive.can.use.UseCan
|
||||||
|
import kr.co.vividnext.sodalive.can.use.UseCanCalculate
|
||||||
|
import kr.co.vividnext.sodalive.can.use.UseCanCalculateRepository
|
||||||
|
import kr.co.vividnext.sodalive.can.use.UseCanCalculateStatus
|
||||||
|
import kr.co.vividnext.sodalive.can.use.UseCanRepository
|
||||||
|
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||||
|
import kr.co.vividnext.sodalive.content.AudioContent
|
||||||
|
import kr.co.vividnext.sodalive.content.AudioContentRepository
|
||||||
|
import kr.co.vividnext.sodalive.content.order.Order
|
||||||
|
import kr.co.vividnext.sodalive.content.order.OrderRepository
|
||||||
|
import kr.co.vividnext.sodalive.content.order.OrderType
|
||||||
|
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunityRepository
|
||||||
|
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||||
|
import kr.co.vividnext.sodalive.live.room.LiveRoomRepository
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.assignment.AgentCreatorRelation
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.assignment.AgentCreatorRelationRepository
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepository
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateService
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.settlement.snapshot.AgentSettlementSnapshotRepository
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
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 java.time.LocalDateTime
|
||||||
|
import javax.persistence.EntityManager
|
||||||
|
|
||||||
|
@DataJpaTest(properties = ["spring.jpa.database-platform=kr.co.vividnext.sodalive.support.H2MySqlFunctionDialect"])
|
||||||
|
@Import(QueryDslConfig::class)
|
||||||
|
class AdminAgentReadParityTest @Autowired constructor(
|
||||||
|
private val queryFactory: JPAQueryFactory,
|
||||||
|
private val memberRepository: MemberRepository,
|
||||||
|
private val relationRepository: AgentCreatorRelationRepository,
|
||||||
|
private val creatorSettlementRatioRepository: CreatorSettlementRatioRepository,
|
||||||
|
private val audioContentRepository: AudioContentRepository,
|
||||||
|
private val orderRepository: OrderRepository,
|
||||||
|
private val liveRoomRepository: LiveRoomRepository,
|
||||||
|
private val creatorCommunityRepository: CreatorCommunityRepository,
|
||||||
|
private val useCanRepository: UseCanRepository,
|
||||||
|
private val useCanCalculateRepository: UseCanCalculateRepository,
|
||||||
|
private val snapshotRepository: AgentSettlementSnapshotRepository,
|
||||||
|
private val entityManager: EntityManager
|
||||||
|
) {
|
||||||
|
private var seededAgentId: Long = 0L
|
||||||
|
private lateinit var agentService: AgentCalculateService
|
||||||
|
private lateinit var adminService: AdminAgentReadService
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
registerMysqlDateFunctions()
|
||||||
|
val queryRepository = AgentCalculateQueryRepository(queryFactory, entityManager)
|
||||||
|
val adminQueryRepository = AdminAgentReadQueryRepository(queryFactory, queryRepository)
|
||||||
|
agentService = AgentCalculateService(queryRepository, snapshotRepository)
|
||||||
|
adminService = AdminAgentReadService(adminQueryRepository, memberRepository, agentService)
|
||||||
|
seededAgentId = seedParityFixtures()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("관리자 라이브 정산 조회는 에이전트 조회와 동일한 응답을 반환한다")
|
||||||
|
fun shouldMatchLiveSettlementBetweenAdminAndAgent() {
|
||||||
|
val agentResponse = agentService.getCalculateLiveByCreator("2026-02-20", "2026-02-20", seededAgentId, 0L, 20L)
|
||||||
|
val adminResponse = adminService.getCalculateLiveByCreator(seededAgentId, "2026-02-20", "2026-02-20", 0L, 20L)
|
||||||
|
|
||||||
|
assertEquals(agentResponse, adminResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("관리자 콘텐츠 정산 조회는 에이전트 조회와 동일한 응답을 반환한다")
|
||||||
|
fun shouldMatchContentSettlementBetweenAdminAndAgent() {
|
||||||
|
val agentResponse = agentService.getCalculateContentByCreator("2026-02-20", "2026-02-20", seededAgentId, 0L, 20L)
|
||||||
|
val adminResponse = adminService.getCalculateContentByCreator(seededAgentId, "2026-02-20", "2026-02-20", 0L, 20L)
|
||||||
|
|
||||||
|
assertEquals(agentResponse.totalCount, adminResponse.totalCount)
|
||||||
|
assertEquals(agentResponse.total, adminResponse.total)
|
||||||
|
assertEquals(agentResponse.items, adminResponse.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("관리자 커뮤니티 정산 조회는 에이전트 조회와 동일한 응답을 반환한다")
|
||||||
|
fun shouldMatchCommunitySettlementBetweenAdminAndAgent() {
|
||||||
|
val agentResponse = agentService.getCalculateCommunityByCreator("2026-02-20", "2026-02-20", seededAgentId, 0L, 20L)
|
||||||
|
val adminResponse = adminService.getCalculateCommunityByCreator(seededAgentId, "2026-02-20", "2026-02-20", 0L, 20L)
|
||||||
|
|
||||||
|
assertEquals(agentResponse.totalCount, adminResponse.totalCount)
|
||||||
|
assertEquals(agentResponse.total, adminResponse.total)
|
||||||
|
assertEquals(agentResponse.items, adminResponse.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("관리자 채널후원 정산 조회는 에이전트 조회와 동일한 응답을 반환한다")
|
||||||
|
fun shouldMatchChannelDonationSettlementBetweenAdminAndAgent() {
|
||||||
|
val agentResponse = agentService.getChannelDonationByCreator("2026-02-20", "2026-02-20", seededAgentId, 0L, 20L)
|
||||||
|
val adminResponse = adminService.getChannelDonationByCreator(seededAgentId, "2026-02-20", "2026-02-20", 0L, 20L)
|
||||||
|
|
||||||
|
assertEquals(agentResponse.totalCount, adminResponse.totalCount)
|
||||||
|
assertEquals(agentResponse.total, adminResponse.total)
|
||||||
|
assertEquals(agentResponse.items, adminResponse.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("관리자 콘텐츠후원 정산 조회는 에이전트 조회와 동일한 응답을 반환한다")
|
||||||
|
fun shouldMatchContentDonationSettlementBetweenAdminAndAgent() {
|
||||||
|
val agentResponse = agentService.getCalculateContentDonationByCreator("2026-02-20", "2026-02-20", seededAgentId, 0L, 20L)
|
||||||
|
val adminResponse = adminService.getCalculateContentDonationByCreator(seededAgentId, "2026-02-20", "2026-02-20", 0L, 20L)
|
||||||
|
|
||||||
|
assertEquals(agentResponse.totalCount, adminResponse.totalCount)
|
||||||
|
assertEquals(agentResponse.total, adminResponse.total)
|
||||||
|
assertEquals(agentResponse.items, adminResponse.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun seedParityFixtures(): Long {
|
||||||
|
val agent = saveMember("agent-parity", MemberRole.AGENT)
|
||||||
|
val creator = saveMember("creator-parity", MemberRole.CREATOR)
|
||||||
|
val buyer = saveMember("buyer-parity", MemberRole.USER)
|
||||||
|
val sender = saveMember("sender-parity", MemberRole.USER)
|
||||||
|
|
||||||
|
saveRelation(agent, creator)
|
||||||
|
saveCreatorSettlementRatio(creator, live = 70, content = 60, community = 65)
|
||||||
|
|
||||||
|
val liveRoom = saveLiveRoom(creator)
|
||||||
|
saveLiveUseCan(sender, liveRoom, 10, LocalDateTime.of(2026, 2, 20, 9, 0, 0))
|
||||||
|
saveLiveUseCan(sender, liveRoom, 20, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||||
|
|
||||||
|
val paidContent = saveAudioContent(creator, "content-parity", price = 50, settlementRatio = 80)
|
||||||
|
val donationContent = saveAudioContent(creator, "content-donation-parity", price = 0, settlementRatio = null)
|
||||||
|
saveOrder(buyer, creator, paidContent, LocalDateTime.of(2026, 2, 20, 11, 0, 0))
|
||||||
|
|
||||||
|
val communityPost = saveCommunityPost(creator, 10)
|
||||||
|
saveCommunityUseCan(buyer, communityPost, 15, LocalDateTime.of(2026, 2, 20, 12, 0, 0))
|
||||||
|
|
||||||
|
saveContentDonationUseCan(buyer, donationContent, 12, LocalDateTime.of(2026, 2, 20, 13, 0, 0))
|
||||||
|
|
||||||
|
val channelDonation = saveChannelDonationUseCan(sender, 40, LocalDateTime.of(2026, 2, 20, 14, 0, 0))
|
||||||
|
saveUseCanCalculate(channelDonation, creator.id!!, 15, PaymentGateway.PG)
|
||||||
|
saveUseCanCalculate(channelDonation, creator.id!!, 25, PaymentGateway.GOOGLE_IAP)
|
||||||
|
|
||||||
|
return agent.id!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveMember(nickname: String, role: MemberRole): Member {
|
||||||
|
return memberRepository.saveAndFlush(
|
||||||
|
Member(
|
||||||
|
email = "$nickname@test.com",
|
||||||
|
password = "password",
|
||||||
|
nickname = nickname,
|
||||||
|
role = role
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveRelation(agent: Member, creator: Member) {
|
||||||
|
val relation = AgentCreatorRelation()
|
||||||
|
relation.agent = agent
|
||||||
|
relation.creator = creator
|
||||||
|
relation.assignedAt = LocalDateTime.of(2026, 2, 1, 0, 0, 0)
|
||||||
|
relation.unassignedAt = null
|
||||||
|
relationRepository.saveAndFlush(relation)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveCreatorSettlementRatio(creator: Member, live: Int, content: Int, community: Int) {
|
||||||
|
val ratio = CreatorSettlementRatio(
|
||||||
|
subsidy = 0,
|
||||||
|
liveSettlementRatio = live,
|
||||||
|
contentSettlementRatio = content,
|
||||||
|
communitySettlementRatio = community
|
||||||
|
)
|
||||||
|
ratio.member = creator
|
||||||
|
creatorSettlementRatioRepository.saveAndFlush(ratio)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveLiveRoom(creator: Member): LiveRoom {
|
||||||
|
val room = LiveRoom(
|
||||||
|
title = "live-room",
|
||||||
|
notice = "notice",
|
||||||
|
beginDateTime = LocalDateTime.of(2026, 2, 20, 8, 0, 0),
|
||||||
|
numberOfPeople = 10,
|
||||||
|
isAdult = false,
|
||||||
|
price = 10
|
||||||
|
)
|
||||||
|
room.member = creator
|
||||||
|
return liveRoomRepository.saveAndFlush(room)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveLiveUseCan(sender: Member, room: LiveRoom, can: Int, createdAt: LocalDateTime): UseCan {
|
||||||
|
val useCan = UseCan(
|
||||||
|
canUsage = CanUsage.LIVE,
|
||||||
|
can = can,
|
||||||
|
rewardCan = 0
|
||||||
|
)
|
||||||
|
useCan.member = sender
|
||||||
|
useCan.room = room
|
||||||
|
val saved = useCanRepository.saveAndFlush(useCan)
|
||||||
|
updateUseCanCreatedAt(saved.id!!, createdAt)
|
||||||
|
return saved
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveAudioContent(creator: Member, title: String, price: Int, settlementRatio: Int?): AudioContent {
|
||||||
|
val theme = AudioContentTheme(
|
||||||
|
theme = "theme-$title",
|
||||||
|
image = "image-$title.png"
|
||||||
|
)
|
||||||
|
entityManager.persist(theme)
|
||||||
|
|
||||||
|
val audioContent = AudioContent(
|
||||||
|
title = title,
|
||||||
|
detail = "detail-$title",
|
||||||
|
languageCode = "ko",
|
||||||
|
price = price,
|
||||||
|
settlementRatio = settlementRatio
|
||||||
|
)
|
||||||
|
audioContent.theme = theme
|
||||||
|
audioContent.member = creator
|
||||||
|
audioContent.isActive = true
|
||||||
|
return audioContentRepository.saveAndFlush(audioContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveOrder(buyer: Member, creator: Member, content: AudioContent, createdAt: LocalDateTime): Order {
|
||||||
|
val order = Order(type = OrderType.KEEP)
|
||||||
|
order.member = buyer
|
||||||
|
order.creator = creator
|
||||||
|
order.audioContent = content
|
||||||
|
val saved = orderRepository.saveAndFlush(order)
|
||||||
|
updateOrderCreatedAt(saved.id!!, createdAt)
|
||||||
|
return saved
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveCommunityPost(creator: Member, price: Int): CreatorCommunity {
|
||||||
|
val post = CreatorCommunity(
|
||||||
|
content = "community-content-$price",
|
||||||
|
price = price,
|
||||||
|
isCommentAvailable = true,
|
||||||
|
isAdult = false
|
||||||
|
)
|
||||||
|
post.member = creator
|
||||||
|
return creatorCommunityRepository.saveAndFlush(post)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveCommunityUseCan(buyer: Member, post: CreatorCommunity, can: Int, createdAt: LocalDateTime): UseCan {
|
||||||
|
val useCan = UseCan(
|
||||||
|
canUsage = CanUsage.PAID_COMMUNITY_POST,
|
||||||
|
can = can,
|
||||||
|
rewardCan = 0
|
||||||
|
)
|
||||||
|
useCan.member = buyer
|
||||||
|
useCan.communityPost = post
|
||||||
|
val saved = useCanRepository.saveAndFlush(useCan)
|
||||||
|
updateUseCanCreatedAt(saved.id!!, createdAt)
|
||||||
|
return saved
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveContentDonationUseCan(buyer: Member, content: AudioContent, can: Int, createdAt: LocalDateTime): UseCan {
|
||||||
|
val useCan = UseCan(
|
||||||
|
canUsage = CanUsage.DONATION,
|
||||||
|
can = can,
|
||||||
|
rewardCan = 0
|
||||||
|
)
|
||||||
|
useCan.member = buyer
|
||||||
|
useCan.audioContent = content
|
||||||
|
val saved = useCanRepository.saveAndFlush(useCan)
|
||||||
|
updateUseCanCreatedAt(saved.id!!, createdAt)
|
||||||
|
return saved
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveChannelDonationUseCan(sender: Member, can: Int, createdAt: LocalDateTime): UseCan {
|
||||||
|
val useCan = UseCan(
|
||||||
|
canUsage = CanUsage.CHANNEL_DONATION,
|
||||||
|
can = can,
|
||||||
|
rewardCan = 0
|
||||||
|
)
|
||||||
|
useCan.member = sender
|
||||||
|
val saved = useCanRepository.saveAndFlush(useCan)
|
||||||
|
updateUseCanCreatedAt(saved.id!!, createdAt)
|
||||||
|
return saved
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveUseCanCalculate(useCan: UseCan, recipientCreatorId: Long, can: Int, paymentGateway: PaymentGateway) {
|
||||||
|
val useCanCalculate = UseCanCalculate(
|
||||||
|
can = can,
|
||||||
|
paymentGateway = paymentGateway,
|
||||||
|
status = UseCanCalculateStatus.RECEIVED
|
||||||
|
)
|
||||||
|
useCanCalculate.useCan = useCan
|
||||||
|
useCanCalculate.recipientCreatorId = recipientCreatorId
|
||||||
|
useCanCalculateRepository.saveAndFlush(useCanCalculate)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUseCanCreatedAt(useCanId: Long, createdAt: LocalDateTime) {
|
||||||
|
entityManager.createQuery("update UseCan u set u.createdAt = :createdAt where u.id = :id")
|
||||||
|
.setParameter("createdAt", createdAt)
|
||||||
|
.setParameter("id", useCanId)
|
||||||
|
.executeUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateOrderCreatedAt(orderId: Long, createdAt: LocalDateTime) {
|
||||||
|
entityManager.createQuery("update Order o set o.createdAt = :createdAt where o.id = :id")
|
||||||
|
.setParameter("createdAt", createdAt)
|
||||||
|
.setParameter("id", orderId)
|
||||||
|
.executeUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerMysqlDateFunctions() {
|
||||||
|
entityManager.createNativeQuery(
|
||||||
|
"CREATE ALIAS IF NOT EXISTS DATE_FORMAT FOR 'kr.co.vividnext.sodalive.support.H2MysqlDateFunctions.dateFormat'"
|
||||||
|
).executeUpdate()
|
||||||
|
entityManager.createNativeQuery(
|
||||||
|
"CREATE ALIAS IF NOT EXISTS CONVERT_TZ FOR 'kr.co.vividnext.sodalive.support.H2MysqlDateFunctions.convertTz'"
|
||||||
|
).executeUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package kr.co.vividnext.sodalive.admin.partner.agent.read
|
||||||
|
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.assignment.AgentCreatorRelation
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.assignment.AgentCreatorRelationRepository
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepository
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
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 java.time.LocalDateTime
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import javax.persistence.EntityManager
|
||||||
|
|
||||||
|
@DataJpaTest(properties = ["spring.jpa.database-platform=kr.co.vividnext.sodalive.support.H2MySqlFunctionDialect"])
|
||||||
|
@Import(QueryDslConfig::class)
|
||||||
|
class AdminAgentReadQueryRepositoryTest @Autowired constructor(
|
||||||
|
private val queryFactory: JPAQueryFactory,
|
||||||
|
private val memberRepository: MemberRepository,
|
||||||
|
private val relationRepository: AgentCreatorRelationRepository,
|
||||||
|
private val entityManager: EntityManager
|
||||||
|
) {
|
||||||
|
private lateinit var repository: AdminAgentReadQueryRepository
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
repository = AdminAgentReadQueryRepository(
|
||||||
|
queryFactory,
|
||||||
|
AgentCalculateQueryRepository(queryFactory, entityManager)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("에이전트 목록 조회는 활성 소속 크리에이터 수를 함께 반환한다")
|
||||||
|
fun shouldGetAgentListWithAssignedCreatorCount() {
|
||||||
|
val agentA = saveMember("agent-a", MemberRole.AGENT)
|
||||||
|
val agentB = saveMember("agent-b", MemberRole.AGENT)
|
||||||
|
val creatorA = saveMember("creator-a", MemberRole.CREATOR)
|
||||||
|
val creatorB = saveMember("creator-b", MemberRole.CREATOR)
|
||||||
|
val now = LocalDateTime.of(2026, 4, 10, 12, 0)
|
||||||
|
|
||||||
|
saveRelation(agentA, creatorA, assignedAt = now.minusDays(1), unassignedAt = null)
|
||||||
|
saveRelation(agentA, creatorB, assignedAt = now.minusDays(2), unassignedAt = now.plusDays(1))
|
||||||
|
|
||||||
|
val totalCount = repository.getAgentListTotalCount()
|
||||||
|
val items = repository.getAgentList(
|
||||||
|
offset = 0,
|
||||||
|
limit = 20,
|
||||||
|
currentTime = ZonedDateTime.of(now, ZoneId.of("Asia/Seoul"))
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(2, totalCount)
|
||||||
|
assertEquals(listOf(agentB.id, agentA.id), items.map { it.agentId })
|
||||||
|
assertEquals(listOf(0, 2), items.map { it.assignedCreatorCount })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("크리에이터 검색은 현재 활성 에이전트 소속 정보를 함께 반환한다")
|
||||||
|
fun shouldSearchAssignableCreatorsWithCurrentAgentInfo() {
|
||||||
|
val agent = saveMember("agent-search", MemberRole.AGENT)
|
||||||
|
val creatorAssigned = saveMember("creator-alpha", MemberRole.CREATOR)
|
||||||
|
val creatorFree = saveMember("creator-beta", MemberRole.CREATOR)
|
||||||
|
val now = LocalDateTime.of(2026, 4, 10, 12, 0)
|
||||||
|
|
||||||
|
saveRelation(agent, creatorAssigned, assignedAt = now.minusDays(1), unassignedAt = null)
|
||||||
|
|
||||||
|
val totalCount = repository.searchAssignableCreatorsTotalCount(searchWord = "creator", currentTime = now)
|
||||||
|
val items = repository.searchAssignableCreators(searchWord = "creator", offset = 0, limit = 20, currentTime = now)
|
||||||
|
|
||||||
|
assertEquals(2, totalCount)
|
||||||
|
assertEquals(listOf(creatorFree.id, creatorAssigned.id), items.map { it.creatorId })
|
||||||
|
assertEquals(listOf(null, agent.id), items.map { it.currentAgentId })
|
||||||
|
assertEquals(listOf(null, "agent-search"), items.map { it.currentAgentNickname })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("특정 에이전트 소속 크리에이터 목록은 assignedAt을 포함해 현재 활성 구간만 반환한다")
|
||||||
|
fun shouldGetAssignedCreatorsForAdminDetail() {
|
||||||
|
val agent = saveMember("agent-detail", MemberRole.AGENT)
|
||||||
|
val activeCreator = saveMember("creator-active", MemberRole.CREATOR)
|
||||||
|
val futureCreator = saveMember("creator-future", MemberRole.CREATOR)
|
||||||
|
val now = LocalDateTime.of(2026, 4, 10, 12, 0)
|
||||||
|
|
||||||
|
saveRelation(agent, activeCreator, assignedAt = now.minusDays(3), unassignedAt = null)
|
||||||
|
saveRelation(agent, futureCreator, assignedAt = now.plusDays(1), unassignedAt = null)
|
||||||
|
|
||||||
|
val totalCount = repository.getAssignedCreatorTotalCount(agentId = agent.id!!, currentTime = now)
|
||||||
|
val items = repository.getAssignedCreators(agentId = agent.id!!, offset = 0, limit = 20, currentTime = now)
|
||||||
|
|
||||||
|
assertEquals(1, totalCount)
|
||||||
|
assertEquals(1, items.size)
|
||||||
|
assertEquals(activeCreator.id, items.first().creatorId)
|
||||||
|
assertEquals(now.minusDays(3), items.first().assignedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveMember(nickname: String, role: MemberRole): Member {
|
||||||
|
return memberRepository.saveAndFlush(
|
||||||
|
Member(
|
||||||
|
email = "$nickname@test.com",
|
||||||
|
password = "password",
|
||||||
|
nickname = nickname,
|
||||||
|
role = role
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveRelation(agent: Member, creator: Member, assignedAt: LocalDateTime, unassignedAt: LocalDateTime?) {
|
||||||
|
val relation = AgentCreatorRelation()
|
||||||
|
relation.agent = agent
|
||||||
|
relation.creator = creator
|
||||||
|
relation.assignedAt = assignedAt
|
||||||
|
relation.unassignedAt = unassignedAt
|
||||||
|
relationRepository.saveAndFlush(relation)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
package kr.co.vividnext.sodalive.admin.partner.agent.read
|
||||||
|
|
||||||
|
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.member.MemberRole
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateService
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.GetAgentSettlementByCreatorItem
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.GetAgentSettlementByCreatorResponse
|
||||||
|
import kr.co.vividnext.sodalive.partner.agent.calculate.GetAgentSettlementByCreatorTotal
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import java.util.Optional
|
||||||
|
|
||||||
|
class AdminAgentReadServiceTest {
|
||||||
|
private lateinit var queryRepository: AdminAgentReadQueryRepository
|
||||||
|
private lateinit var memberRepository: MemberRepository
|
||||||
|
private lateinit var calculateService: AgentCalculateService
|
||||||
|
private lateinit var service: AdminAgentReadService
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
queryRepository = Mockito.mock(AdminAgentReadQueryRepository::class.java)
|
||||||
|
memberRepository = Mockito.mock(MemberRepository::class.java)
|
||||||
|
calculateService = Mockito.mock(AgentCalculateService::class.java)
|
||||||
|
service = AdminAgentReadService(queryRepository, memberRepository, calculateService)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("크리에이터 검색은 두 글자 미만 검색어를 거부한다")
|
||||||
|
fun shouldRejectTooShortSearchWord() {
|
||||||
|
val exception = assertThrows(SodaException::class.java) {
|
||||||
|
service.searchAssignableCreators(searchWord = "a", offset = 0, limit = 20)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("admin.member.search_word_min_length", exception.messageKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("에이전트 목록 조회는 현재 월 summary 필드를 그대로 반환한다")
|
||||||
|
fun shouldReturnAgentListWithCurrentMonthSummaryFields() {
|
||||||
|
Mockito.`when`(queryRepository.getAgentListTotalCount()).thenReturn(1)
|
||||||
|
Mockito.doReturn(
|
||||||
|
listOf(
|
||||||
|
GetAdminAgentListItem(
|
||||||
|
agentId = 11L,
|
||||||
|
agentNickname = "agent-a",
|
||||||
|
assignedCreatorCount = 2,
|
||||||
|
liveAgentSettlementAmount = 131,
|
||||||
|
contentAgentSettlementAmount = 80,
|
||||||
|
communityAgentSettlementAmount = 55,
|
||||||
|
contentDonationAgentSettlementAmount = 12,
|
||||||
|
channelDonationAgentSettlementAmount = 397
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).`when`(queryRepository).getAgentList(
|
||||||
|
Mockito.eq(0L),
|
||||||
|
Mockito.eq(20L),
|
||||||
|
Mockito.any(ZonedDateTime::class.java) ?: ZonedDateTime.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
val actual = service.getAgentList(offset = 0L, limit = 20L)
|
||||||
|
|
||||||
|
assertEquals(397, actual.items.first().channelDonationAgentSettlementAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("소속 크리에이터 목록은 AGENT 대상만 조회한다")
|
||||||
|
fun shouldThrowWhenTargetMemberIsNotAgent() {
|
||||||
|
val creator = Member(
|
||||||
|
email = "creator@test.com",
|
||||||
|
password = "password",
|
||||||
|
nickname = "creator",
|
||||||
|
role = MemberRole.CREATOR
|
||||||
|
)
|
||||||
|
creator.id = 7L
|
||||||
|
|
||||||
|
Mockito.`when`(memberRepository.findById(7L)).thenReturn(Optional.of(creator))
|
||||||
|
|
||||||
|
val exception = assertThrows(SodaException::class.java) {
|
||||||
|
service.getAssignedCreators(agentId = 7L, offset = 0, limit = 20)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("partner.agent.ratio.invalid_agent", exception.messageKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("라이브 정산 상세 조회는 기존 AgentCalculateService로 위임한다")
|
||||||
|
fun shouldDelegateLiveSettlementToAgentCalculateService() {
|
||||||
|
val agent = Member(
|
||||||
|
email = "agent@test.com",
|
||||||
|
password = "password",
|
||||||
|
nickname = "agent",
|
||||||
|
role = MemberRole.AGENT
|
||||||
|
)
|
||||||
|
agent.id = 11L
|
||||||
|
|
||||||
|
val response = GetAgentSettlementByCreatorResponse(
|
||||||
|
totalCount = 1,
|
||||||
|
total = GetAgentSettlementByCreatorTotal(
|
||||||
|
count = 1,
|
||||||
|
totalCan = 50,
|
||||||
|
krw = 5000,
|
||||||
|
fee = 330,
|
||||||
|
settlementAmount = 3736,
|
||||||
|
tax = 123,
|
||||||
|
depositAmount = 3613,
|
||||||
|
agentSettlementAmount = 131
|
||||||
|
),
|
||||||
|
items = listOf(
|
||||||
|
GetAgentSettlementByCreatorItem(
|
||||||
|
creatorId = 21L,
|
||||||
|
creatorNickname = "creator-a",
|
||||||
|
count = 1,
|
||||||
|
totalCan = 50,
|
||||||
|
krw = 5000,
|
||||||
|
fee = 330,
|
||||||
|
settlementAmount = 3736,
|
||||||
|
tax = 123,
|
||||||
|
depositAmount = 3613,
|
||||||
|
agentSettlementAmount = 131
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Mockito.`when`(memberRepository.findById(11L)).thenReturn(Optional.of(agent))
|
||||||
|
Mockito.`when`(
|
||||||
|
calculateService.getCalculateLiveByCreator(
|
||||||
|
startDateStr = "2026-04-01",
|
||||||
|
endDateStr = "2026-04-30",
|
||||||
|
agentId = 11L,
|
||||||
|
offset = 0L,
|
||||||
|
limit = 20L
|
||||||
|
)
|
||||||
|
).thenReturn(response)
|
||||||
|
|
||||||
|
val actual = service.getCalculateLiveByCreator(
|
||||||
|
agentId = 11L,
|
||||||
|
startDateStr = "2026-04-01",
|
||||||
|
endDateStr = "2026-04-30",
|
||||||
|
offset = 0L,
|
||||||
|
limit = 20L
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(131, actual.items.first().agentSettlementAmount)
|
||||||
|
Mockito.verify(calculateService).getCalculateLiveByCreator(
|
||||||
|
startDateStr = "2026-04-01",
|
||||||
|
endDateStr = "2026-04-30",
|
||||||
|
agentId = 11L,
|
||||||
|
offset = 0L,
|
||||||
|
limit = 20L
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user