feat(agent-calculate): 에이전트별 정산 조회 기능을 추가한다
This commit is contained in:
@@ -0,0 +1,326 @@
|
||||
package kr.co.vividnext.sodalive.partner.agent.calculate
|
||||
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
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 org.springframework.data.domain.PageRequest
|
||||
|
||||
class AgentCalculateControllerTest {
|
||||
private lateinit var service: AgentCalculateService
|
||||
private lateinit var controller: AgentCalculateController
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
service = Mockito.mock(AgentCalculateService::class.java)
|
||||
controller = AgentCalculateController(service)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("인증 사용자 정보가 없으면 소속 크리에이터 목록 조회는 예외를 던진다")
|
||||
fun shouldThrowWhenMemberIsNullForAssignedCreators() {
|
||||
val exception = assertThrows(SodaException::class.java) {
|
||||
controller.getAssignedCreators(
|
||||
member = null,
|
||||
pageable = PageRequest.of(0, 10)
|
||||
)
|
||||
}
|
||||
|
||||
assertEquals("common.error.bad_credentials", exception.messageKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("에이전트 컨트롤러는 소속 크리에이터 목록 조회 파라미터를 서비스로 전달한다")
|
||||
fun shouldForwardAssignedCreatorsRequestToService() {
|
||||
val member = createAgentMember(7L)
|
||||
val response = GetAgentAssignedCreatorResponse(
|
||||
totalCount = 1,
|
||||
items = listOf(
|
||||
GetAgentAssignedCreatorItem(
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Mockito.`when`(service.getAssignedCreators(agentId = 7L, offset = 10L, limit = 5L)).thenReturn(response)
|
||||
|
||||
val apiResponse = controller.getAssignedCreators(
|
||||
member = member,
|
||||
pageable = PageRequest.of(2, 5)
|
||||
)
|
||||
|
||||
assertEquals(true, apiResponse.success)
|
||||
assertEquals(1, apiResponse.data!!.totalCount)
|
||||
assertEquals(21L, apiResponse.data!!.items[0].creatorId)
|
||||
Mockito.verify(service).getAssignedCreators(agentId = 7L, offset = 10L, limit = 5L)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("에이전트 컨트롤러는 라이브 요약 조회 파라미터를 서비스로 전달한다")
|
||||
fun shouldForwardLiveSummaryRequestToService() {
|
||||
val member = createAgentMember(7L)
|
||||
val response = GetAgentSettlementByCreatorResponse(
|
||||
totalCount = 1,
|
||||
total = GetAgentSettlementByCreatorTotal(
|
||||
count = 2,
|
||||
totalCan = 40,
|
||||
krw = 4000,
|
||||
fee = 264,
|
||||
settlementAmount = 2615,
|
||||
tax = 86,
|
||||
depositAmount = 2529,
|
||||
agentSettlementAmount = 262
|
||||
),
|
||||
items = listOf(
|
||||
GetAgentSettlementByCreatorItem(
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a",
|
||||
count = 2,
|
||||
totalCan = 40,
|
||||
krw = 4000,
|
||||
fee = 264,
|
||||
settlementAmount = 2615,
|
||||
tax = 86,
|
||||
depositAmount = 2529,
|
||||
agentSettlementAmount = 262
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Mockito.`when`(
|
||||
service.getCalculateLiveByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 20L
|
||||
)
|
||||
).thenReturn(response)
|
||||
|
||||
val apiResponse = controller.getCalculateLiveByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
member = member,
|
||||
pageable = PageRequest.of(0, 20)
|
||||
)
|
||||
|
||||
assertEquals(true, apiResponse.success)
|
||||
assertEquals(262, apiResponse.data!!.items[0].agentSettlementAmount)
|
||||
Mockito.verify(service).getCalculateLiveByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 20L
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("에이전트 컨트롤러는 콘텐츠 요약 조회 파라미터를 서비스로 전달한다")
|
||||
fun shouldForwardContentSummaryRequestToService() {
|
||||
val member = createAgentMember(7L)
|
||||
val response = createGenericSummaryResponse(agentSettlementAmount = 374)
|
||||
|
||||
Mockito.`when`(
|
||||
service.getCalculateContentByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 20L,
|
||||
limit = 20L
|
||||
)
|
||||
).thenReturn(response)
|
||||
|
||||
val apiResponse = controller.getCalculateContentByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
member = member,
|
||||
pageable = PageRequest.of(1, 20)
|
||||
)
|
||||
|
||||
assertEquals(true, apiResponse.success)
|
||||
assertEquals(374, apiResponse.data!!.items[0].agentSettlementAmount)
|
||||
Mockito.verify(service).getCalculateContentByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 20L,
|
||||
limit = 20L
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("에이전트 컨트롤러는 커뮤니티 요약 조회 파라미터를 서비스로 전달한다")
|
||||
fun shouldForwardCommunitySummaryRequestToService() {
|
||||
val member = createAgentMember(7L)
|
||||
val response = createGenericSummaryResponse(agentSettlementAmount = 168)
|
||||
|
||||
Mockito.`when`(
|
||||
service.getCalculateCommunityByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 10L
|
||||
)
|
||||
).thenReturn(response)
|
||||
|
||||
val apiResponse = controller.getCalculateCommunityByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
member = member,
|
||||
pageable = PageRequest.of(0, 10)
|
||||
)
|
||||
|
||||
assertEquals(true, apiResponse.success)
|
||||
assertEquals(168, apiResponse.data!!.items[0].agentSettlementAmount)
|
||||
Mockito.verify(service).getCalculateCommunityByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 10L
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("에이전트 컨트롤러는 채널후원 요약 조회 파라미터를 서비스로 전달한다")
|
||||
fun shouldForwardChannelDonationSummaryRequestToService() {
|
||||
val member = createAgentMember(7L)
|
||||
val response = GetAgentChannelDonationSettlementByCreatorResponse(
|
||||
totalCount = 1,
|
||||
total = GetAgentChannelDonationSettlementTotal(
|
||||
count = 1,
|
||||
totalCan = 50,
|
||||
krw = 5000,
|
||||
fee = 330,
|
||||
settlementAmount = 3970,
|
||||
withholdingTax = 131,
|
||||
depositAmount = 3839,
|
||||
agentSettlementAmount = 397
|
||||
),
|
||||
items = listOf(
|
||||
GetAgentChannelDonationSettlementByCreatorItem(
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a",
|
||||
count = 1,
|
||||
totalCan = 50,
|
||||
krw = 5000,
|
||||
fee = 330,
|
||||
settlementAmount = 3970,
|
||||
withholdingTax = 131,
|
||||
depositAmount = 3839,
|
||||
agentSettlementAmount = 397
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Mockito.`when`(
|
||||
service.getChannelDonationByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 10L
|
||||
)
|
||||
).thenReturn(response)
|
||||
|
||||
val apiResponse = controller.getChannelDonationByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
member = member,
|
||||
pageable = PageRequest.of(0, 10)
|
||||
)
|
||||
|
||||
assertEquals(true, apiResponse.success)
|
||||
assertEquals(397, apiResponse.data!!.items[0].agentSettlementAmount)
|
||||
Mockito.verify(service).getChannelDonationByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 10L
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("에이전트 컨트롤러는 콘텐츠후원 요약 조회 파라미터를 서비스로 전달한다")
|
||||
fun shouldForwardContentDonationSummaryRequestToService() {
|
||||
val member = createAgentMember(7L)
|
||||
val response = createGenericSummaryResponse(agentSettlementAmount = 131)
|
||||
|
||||
Mockito.`when`(
|
||||
service.getCalculateContentDonationByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 10L
|
||||
)
|
||||
).thenReturn(response)
|
||||
|
||||
val apiResponse = controller.getCalculateContentDonationByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
member = member,
|
||||
pageable = PageRequest.of(0, 10)
|
||||
)
|
||||
|
||||
assertEquals(true, apiResponse.success)
|
||||
assertEquals(131, apiResponse.data!!.items[0].agentSettlementAmount)
|
||||
Mockito.verify(service).getCalculateContentDonationByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 10L
|
||||
)
|
||||
}
|
||||
|
||||
private fun createAgentMember(id: Long): Member {
|
||||
val member = Member(
|
||||
email = "agent@test.com",
|
||||
password = "password",
|
||||
nickname = "agent",
|
||||
role = MemberRole.AGENT
|
||||
)
|
||||
member.id = id
|
||||
return member
|
||||
}
|
||||
|
||||
private fun createGenericSummaryResponse(agentSettlementAmount: Int): GetAgentSettlementByCreatorResponse {
|
||||
return GetAgentSettlementByCreatorResponse(
|
||||
totalCount = 1,
|
||||
total = GetAgentSettlementByCreatorTotal(
|
||||
count = 1,
|
||||
totalCan = 50,
|
||||
krw = 5000,
|
||||
fee = 330,
|
||||
settlementAmount = 3736,
|
||||
tax = 123,
|
||||
depositAmount = 3613,
|
||||
agentSettlementAmount = agentSettlementAmount
|
||||
),
|
||||
items = listOf(
|
||||
GetAgentSettlementByCreatorItem(
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a",
|
||||
count = 1,
|
||||
totalCan = 50,
|
||||
krw = 5000,
|
||||
fee = 330,
|
||||
settlementAmount = 3736,
|
||||
tax = 123,
|
||||
depositAmount = 3613,
|
||||
agentSettlementAmount = agentSettlementAmount
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,777 @@
|
||||
package kr.co.vividnext.sodalive.partner.agent.calculate
|
||||
|
||||
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.ratio.AgentSettlementRatio
|
||||
import kr.co.vividnext.sodalive.partner.agent.ratio.AgentSettlementRatioRepository
|
||||
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 AgentCalculateQueryRepositoryTest @Autowired constructor(
|
||||
private val queryFactory: JPAQueryFactory,
|
||||
private val memberRepository: MemberRepository,
|
||||
private val relationRepository: AgentCreatorRelationRepository,
|
||||
private val agentSettlementRatioRepository: AgentSettlementRatioRepository,
|
||||
private val snapshotRepository: AgentSettlementSnapshotRepository,
|
||||
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 entityManager: EntityManager
|
||||
) {
|
||||
private lateinit var repository: AgentCalculateQueryRepository
|
||||
private lateinit var service: AgentCalculateService
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
registerMysqlDateFunctions()
|
||||
repository = AgentCalculateQueryRepository(queryFactory)
|
||||
service = AgentCalculateService(repository, snapshotRepository)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("소속 크리에이터 목록 조회는 현재 에이전트에 연결된 크리에이터만 반환한다")
|
||||
fun shouldGetAssignedCreatorsOnlyForCurrentAgent() {
|
||||
val agent = saveMember("agent-a", MemberRole.AGENT)
|
||||
val otherAgent = saveMember("agent-b", MemberRole.AGENT)
|
||||
val creatorA = saveMember("creator-a", MemberRole.CREATOR)
|
||||
val creatorB = saveMember("creator-b", MemberRole.CREATOR)
|
||||
val creatorC = saveMember("creator-c", MemberRole.CREATOR)
|
||||
val currentTime = LocalDateTime.of(2026, 2, 20, 12, 0, 0)
|
||||
|
||||
saveRelation(agent, creatorA)
|
||||
saveRelation(agent, creatorB)
|
||||
saveRelation(otherAgent, creatorC)
|
||||
|
||||
val totalCount = repository.getAssignedCreatorTotalCount(agent.id!!, currentTime)
|
||||
val items = repository.getAssignedCreators(agent.id!!, offset = 0, limit = 10, currentTime = currentTime)
|
||||
|
||||
assertEquals(2, totalCount)
|
||||
assertEquals(2, items.size)
|
||||
assertEquals(listOf(creatorB.id, creatorA.id), items.map { it.creatorId })
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("소속 크리에이터 목록 조회는 현재 시각 활성 구간의 크리에이터만 반환한다")
|
||||
fun shouldGetAssignedCreatorsOnlyWithinCurrentAssignmentWindow() {
|
||||
val agent = saveMember("agent-window", MemberRole.AGENT)
|
||||
val currentCreator = saveMember("creator-current", MemberRole.CREATOR)
|
||||
val futureCreator = saveMember("creator-future", MemberRole.CREATOR)
|
||||
val endingFutureCreator = saveMember("creator-ending-future", MemberRole.CREATOR)
|
||||
val endedCreator = saveMember("creator-ended", MemberRole.CREATOR)
|
||||
val currentTime = LocalDateTime.now()
|
||||
|
||||
saveRelation(agent, currentCreator, assignedAt = currentTime.minusDays(2), unassignedAt = null)
|
||||
saveRelation(agent, futureCreator, assignedAt = currentTime.plusDays(1), unassignedAt = null)
|
||||
saveRelation(agent, endingFutureCreator, assignedAt = currentTime.minusDays(2), unassignedAt = currentTime.plusDays(1))
|
||||
saveRelation(agent, endedCreator, assignedAt = currentTime.minusDays(3), unassignedAt = currentTime.minusHours(1))
|
||||
|
||||
val totalCount = repository.getAssignedCreatorTotalCount(agent.id!!, currentTime)
|
||||
val items = repository.getAssignedCreators(agent.id!!, offset = 0, limit = 10, currentTime = currentTime)
|
||||
|
||||
assertEquals(2, totalCount)
|
||||
assertEquals(listOf(endingFutureCreator.id, currentCreator.id), items.map { it.creatorId })
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("라이브 크리에이터별 조회는 소속된 크리에이터만 집계한다")
|
||||
fun shouldGetLiveSummaryRowsOnlyForAssignedCreators() {
|
||||
val agent = saveMember("agent-live", MemberRole.AGENT)
|
||||
val creator = saveMember("creator-live", MemberRole.CREATOR)
|
||||
val otherCreator = saveMember("creator-other-live", MemberRole.CREATOR)
|
||||
val sender = saveMember("sender-live", MemberRole.USER)
|
||||
|
||||
saveRelation(agent, creator)
|
||||
saveCreatorSettlementRatio(creator, live = 70, content = 70, community = 70)
|
||||
saveCreatorSettlementRatio(otherCreator, live = 80, content = 80, community = 80)
|
||||
|
||||
val room = saveLiveRoom(creator)
|
||||
val otherRoom = saveLiveRoom(otherCreator)
|
||||
saveLiveUseCan(sender, room, 10, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||
saveLiveUseCan(sender, room, 30, LocalDateTime.of(2026, 2, 20, 11, 0, 0))
|
||||
saveLiveUseCan(sender, otherRoom, 50, LocalDateTime.of(2026, 2, 20, 12, 0, 0))
|
||||
|
||||
val startDate = LocalDateTime.of(2026, 2, 20, 0, 0, 0)
|
||||
val endDate = LocalDateTime.of(2026, 2, 20, 23, 59, 59)
|
||||
|
||||
val totalCount = repository.getCalculateLiveByCreatorTotalCount(startDate, endDate, agent.id!!)
|
||||
val rows = repository.getCalculateLiveByCreator(startDate, endDate, agent.id!!)
|
||||
|
||||
assertEquals(1, totalCount)
|
||||
assertEquals(1, rows.size)
|
||||
assertEquals(creator.id, rows[0].creatorId)
|
||||
assertEquals(2L, rows[0].count)
|
||||
assertEquals(40, rows[0].totalCan)
|
||||
assertEquals(70, rows[0].settlementRatio)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("콘텐츠 크리에이터별 조회는 콘텐츠 개별 정산 비율과 기본 정산 비율을 모두 반영한다")
|
||||
fun shouldGetContentSummaryRowsGroupedByEffectiveSettlementRatio() {
|
||||
val agent = saveMember("agent-content", MemberRole.AGENT)
|
||||
val creator = saveMember("creator-content", MemberRole.CREATOR)
|
||||
val otherCreator = saveMember("creator-other-content", MemberRole.CREATOR)
|
||||
val buyer = saveMember("buyer-content", MemberRole.USER)
|
||||
|
||||
saveRelation(agent, creator)
|
||||
saveCreatorSettlementRatio(creator, live = 70, content = 60, community = 70)
|
||||
saveCreatorSettlementRatio(otherCreator, live = 70, content = 90, community = 70)
|
||||
|
||||
val paidContent = saveAudioContent(creator, "content-a", price = 50, settlementRatio = 80)
|
||||
val fallbackContent = saveAudioContent(creator, "content-b", price = 30, settlementRatio = null)
|
||||
val otherContent = saveAudioContent(otherCreator, "content-c", price = 90, settlementRatio = null)
|
||||
|
||||
saveOrder(buyer, creator, paidContent, LocalDateTime.of(2026, 2, 20, 9, 0, 0))
|
||||
saveOrder(buyer, creator, fallbackContent, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||
saveOrder(buyer, otherCreator, otherContent, LocalDateTime.of(2026, 2, 20, 11, 0, 0))
|
||||
|
||||
val startDate = LocalDateTime.of(2026, 2, 20, 0, 0, 0)
|
||||
val endDate = LocalDateTime.of(2026, 2, 20, 23, 59, 59)
|
||||
|
||||
val totalCount = repository.getCalculateContentByCreatorTotalCount(startDate, endDate, agent.id!!)
|
||||
val rows = repository.getCalculateContentByCreator(startDate, endDate, agent.id!!)
|
||||
|
||||
assertEquals(1, totalCount)
|
||||
assertEquals(2, rows.size)
|
||||
assertEquals(listOf(80, 60), rows.mapNotNull { it.settlementRatio }.sortedDescending())
|
||||
assertEquals(listOf(50, 30), rows.map { it.totalCan }.sortedDescending())
|
||||
assertEquals(listOf(creator.id!!, creator.id!!), rows.map { it.creatorId })
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("커뮤니티 크리에이터별 조회는 소속된 크리에이터만 집계한다")
|
||||
fun shouldGetCommunitySummaryRowsOnlyForAssignedCreators() {
|
||||
val agent = saveMember("agent-community", MemberRole.AGENT)
|
||||
val creator = saveMember("creator-community", MemberRole.CREATOR)
|
||||
val otherCreator = saveMember("creator-other-community", MemberRole.CREATOR)
|
||||
val buyer = saveMember("buyer-community", MemberRole.USER)
|
||||
|
||||
saveRelation(agent, creator)
|
||||
saveCreatorSettlementRatio(creator, live = 70, content = 70, community = 60)
|
||||
saveCreatorSettlementRatio(otherCreator, live = 70, content = 70, community = 80)
|
||||
|
||||
val communityPost = saveCommunityPost(creator, 10)
|
||||
val otherPost = saveCommunityPost(otherCreator, 20)
|
||||
|
||||
saveCommunityUseCan(buyer, communityPost, 20, LocalDateTime.of(2026, 2, 20, 9, 0, 0))
|
||||
saveCommunityUseCan(buyer, communityPost, 10, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||
saveCommunityUseCan(buyer, otherPost, 50, LocalDateTime.of(2026, 2, 20, 11, 0, 0))
|
||||
|
||||
val startDate = LocalDateTime.of(2026, 2, 20, 0, 0, 0)
|
||||
val endDate = LocalDateTime.of(2026, 2, 20, 23, 59, 59)
|
||||
|
||||
val totalCount = repository.getCalculateCommunityByCreatorTotalCount(startDate, endDate, agent.id!!)
|
||||
val rows = repository.getCalculateCommunityByCreator(startDate, endDate, agent.id!!)
|
||||
|
||||
assertEquals(1, totalCount)
|
||||
assertEquals(1, rows.size)
|
||||
assertEquals(creator.id, rows[0].creatorId)
|
||||
assertEquals(2L, rows[0].count)
|
||||
assertEquals(30, rows[0].totalCan)
|
||||
assertEquals(60, rows[0].settlementRatio)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("콘텐츠후원 크리에이터별 조회는 소속된 크리에이터만 집계한다")
|
||||
fun shouldGetContentDonationSummaryRowsOnlyForAssignedCreators() {
|
||||
val agent = saveMember("agent-content-donation", MemberRole.AGENT)
|
||||
val creator = saveMember("creator-content-donation", MemberRole.CREATOR)
|
||||
val otherCreator = saveMember("creator-other-content-donation", MemberRole.CREATOR)
|
||||
val buyer = saveMember("buyer-content-donation", MemberRole.USER)
|
||||
|
||||
saveRelation(agent, creator)
|
||||
|
||||
val content = saveAudioContent(creator, "content-donation-a", price = 0, settlementRatio = null)
|
||||
val otherContent = saveAudioContent(otherCreator, "content-donation-b", price = 0, settlementRatio = null)
|
||||
|
||||
saveContentDonationUseCan(buyer, content, 7, LocalDateTime.of(2026, 2, 20, 9, 0, 0))
|
||||
saveContentDonationUseCan(buyer, content, 13, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||
saveContentDonationUseCan(buyer, otherContent, 30, LocalDateTime.of(2026, 2, 20, 11, 0, 0))
|
||||
|
||||
val startDate = LocalDateTime.of(2026, 2, 20, 0, 0, 0)
|
||||
val endDate = LocalDateTime.of(2026, 2, 20, 23, 59, 59)
|
||||
|
||||
val totalCount = repository.getCalculateContentDonationByCreatorTotalCount(startDate, endDate, agent.id!!)
|
||||
val rows = repository.getCalculateContentDonationByCreator(startDate, endDate, agent.id!!)
|
||||
|
||||
assertEquals(1, totalCount)
|
||||
assertEquals(1, rows.size)
|
||||
assertEquals(creator.id, rows[0].creatorId)
|
||||
assertEquals(2L, rows[0].count)
|
||||
assertEquals(20, rows[0].totalCan)
|
||||
assertEquals(70, rows[0].settlementRatio)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("채널후원 크리에이터별 조회는 분할 정산 레코드가 있어도 후원 단위로 집계한다")
|
||||
fun shouldCountDistinctUseCanForChannelDonationByCreator() {
|
||||
val agent = saveMember("agent-channel", MemberRole.AGENT)
|
||||
val creator = saveMember("creator-channel", MemberRole.CREATOR)
|
||||
val otherCreator = saveMember("creator-other-channel", MemberRole.CREATOR)
|
||||
val sender = saveMember("sender-channel", MemberRole.USER)
|
||||
|
||||
saveRelation(agent, creator)
|
||||
|
||||
val useCan = saveChannelDonationUseCan(sender, 50, LocalDateTime.of(2026, 2, 20, 9, 0, 0))
|
||||
saveUseCanCalculate(useCan, creator.id!!, 20, PaymentGateway.PG)
|
||||
saveUseCanCalculate(useCan, creator.id!!, 30, PaymentGateway.GOOGLE_IAP)
|
||||
|
||||
val otherUseCan = saveChannelDonationUseCan(sender, 40, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||
saveUseCanCalculate(otherUseCan, otherCreator.id!!, 40, PaymentGateway.PG)
|
||||
|
||||
val startDate = LocalDateTime.of(2026, 2, 20, 0, 0, 0)
|
||||
val endDate = LocalDateTime.of(2026, 2, 20, 23, 59, 59)
|
||||
|
||||
val totalCount = repository.getChannelDonationByCreatorTotalCount(startDate, endDate, agent.id!!)
|
||||
val rows = repository.getChannelDonationByCreator(startDate, endDate, agent.id!!)
|
||||
|
||||
assertEquals(1, totalCount)
|
||||
assertEquals(1, rows.size)
|
||||
assertEquals(creator.id, rows[0].creatorId)
|
||||
assertEquals(1L, rows[0].count)
|
||||
assertEquals(50, rows[0].totalCan)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("페이지 대상 creator가 없으면 모든 카테고리 조회는 빈 rows를 반환한다")
|
||||
fun shouldReturnEmptyRowsWhenPagedCreatorSelectionIsEmptyAcrossAllCategories() {
|
||||
val agent = saveMember("agent-empty-page", MemberRole.AGENT)
|
||||
val creator = saveMember("creator-empty-page", MemberRole.CREATOR)
|
||||
val buyer = saveMember("buyer-empty-page", MemberRole.USER)
|
||||
|
||||
saveRelation(agent, creator)
|
||||
saveAgentSettlementRatio(agent, settlementRatio = 10, effectiveFrom = LocalDateTime.of(2026, 2, 1, 0, 0, 0))
|
||||
saveCreatorSettlementRatio(creator, live = 70, content = 70, community = 70)
|
||||
|
||||
val room = saveLiveRoom(creator)
|
||||
saveLiveUseCan(buyer, room, 10, LocalDateTime.of(2026, 2, 20, 9, 0, 0))
|
||||
|
||||
val content = saveAudioContent(creator, "empty-page-content", price = 10, settlementRatio = null)
|
||||
saveOrder(buyer, creator, content, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||
|
||||
val communityPost = saveCommunityPost(creator, 10)
|
||||
saveCommunityUseCan(buyer, communityPost, 10, LocalDateTime.of(2026, 2, 20, 11, 0, 0))
|
||||
|
||||
saveContentDonationUseCan(buyer, content, 10, LocalDateTime.of(2026, 2, 20, 12, 0, 0))
|
||||
|
||||
val channelDonation = saveChannelDonationUseCan(buyer, 10, LocalDateTime.of(2026, 2, 20, 13, 0, 0))
|
||||
saveUseCanCalculate(channelDonation, creator.id!!, 10, PaymentGateway.PG)
|
||||
|
||||
val startDate = LocalDateTime.of(2026, 2, 20, 0, 0, 0)
|
||||
val endDate = LocalDateTime.of(2026, 2, 20, 23, 59, 59)
|
||||
|
||||
assertEquals(1, repository.getCalculateLiveByCreatorTotalCount(startDate, endDate, agent.id!!))
|
||||
assertEquals(1, repository.getCalculateContentByCreatorTotalCount(startDate, endDate, agent.id!!))
|
||||
assertEquals(1, repository.getCalculateCommunityByCreatorTotalCount(startDate, endDate, agent.id!!))
|
||||
assertEquals(1, repository.getCalculateContentDonationByCreatorTotalCount(startDate, endDate, agent.id!!))
|
||||
assertEquals(1, repository.getChannelDonationByCreatorTotalCount(startDate, endDate, agent.id!!))
|
||||
|
||||
assertEquals(
|
||||
0,
|
||||
repository.getCalculateLiveByCreator(startDate, endDate, agent.id!!, offset = 1, limit = 10).size
|
||||
)
|
||||
assertEquals(
|
||||
0,
|
||||
repository.getCalculateContentByCreator(startDate, endDate, agent.id!!, offset = 1, limit = 10).size
|
||||
)
|
||||
assertEquals(
|
||||
0,
|
||||
repository.getCalculateCommunityByCreator(startDate, endDate, agent.id!!, offset = 1, limit = 10).size
|
||||
)
|
||||
assertEquals(
|
||||
0,
|
||||
repository.getCalculateContentDonationByCreator(startDate, endDate, agent.id!!, offset = 1, limit = 10).size
|
||||
)
|
||||
assertEquals(
|
||||
0,
|
||||
repository.getChannelDonationByCreator(startDate, endDate, agent.id!!, offset = 1, limit = 10).size
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("정산 조회는 기간 중 소속 변경이 있어도 거래 시점 기준 소속 에이전트에게만 반영한다")
|
||||
fun shouldResolveAssignmentAtEventTimeAcrossAllSettlementCategories() {
|
||||
val firstAgent = saveMember("agent-assignment-a", MemberRole.AGENT)
|
||||
val secondAgent = saveMember("agent-assignment-b", MemberRole.AGENT)
|
||||
val creator = saveMember("creator-assignment", MemberRole.CREATOR)
|
||||
val buyer = saveMember("buyer-assignment", MemberRole.USER)
|
||||
|
||||
saveAgentSettlementRatio(firstAgent, settlementRatio = 10, effectiveFrom = LocalDateTime.of(2026, 2, 1, 0, 0, 0))
|
||||
saveAgentSettlementRatio(secondAgent, settlementRatio = 10, effectiveFrom = LocalDateTime.of(2026, 2, 1, 0, 0, 0))
|
||||
saveRelation(
|
||||
agent = firstAgent,
|
||||
creator = creator,
|
||||
assignedAt = LocalDateTime.of(2026, 2, 1, 0, 0, 0),
|
||||
unassignedAt = LocalDateTime.of(2026, 2, 20, 12, 0, 0)
|
||||
)
|
||||
saveRelation(
|
||||
agent = secondAgent,
|
||||
creator = creator,
|
||||
assignedAt = LocalDateTime.of(2026, 2, 20, 12, 0, 0),
|
||||
unassignedAt = null
|
||||
)
|
||||
saveCreatorSettlementRatio(creator, live = 70, content = 70, community = 70)
|
||||
|
||||
val room = saveLiveRoom(creator)
|
||||
saveLiveUseCan(buyer, room, 10, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||
saveLiveUseCan(buyer, room, 20, LocalDateTime.of(2026, 2, 20, 14, 0, 0))
|
||||
|
||||
val contentBefore = saveAudioContent(creator, "assignment-content-before", price = 10, settlementRatio = null)
|
||||
val contentAfter = saveAudioContent(creator, "assignment-content-after", price = 20, settlementRatio = null)
|
||||
saveOrder(buyer, creator, contentBefore, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||
saveOrder(buyer, creator, contentAfter, LocalDateTime.of(2026, 2, 20, 14, 0, 0))
|
||||
|
||||
val communityBefore = saveCommunityPost(creator, 10)
|
||||
val communityAfter = saveCommunityPost(creator, 20)
|
||||
saveCommunityUseCan(buyer, communityBefore, 10, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||
saveCommunityUseCan(buyer, communityAfter, 20, LocalDateTime.of(2026, 2, 20, 14, 0, 0))
|
||||
|
||||
saveContentDonationUseCan(buyer, contentBefore, 10, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||
saveContentDonationUseCan(buyer, contentAfter, 20, LocalDateTime.of(2026, 2, 20, 14, 0, 0))
|
||||
|
||||
val channelBefore = saveChannelDonationUseCan(buyer, 10, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||
saveUseCanCalculate(channelBefore, creator.id!!, 10, PaymentGateway.PG)
|
||||
val channelAfter = saveChannelDonationUseCan(buyer, 20, LocalDateTime.of(2026, 2, 20, 14, 0, 0))
|
||||
saveUseCanCalculate(channelAfter, creator.id!!, 20, PaymentGateway.PG)
|
||||
|
||||
val savedRelations = relationRepository.findAllByCreatorIdOrderByAssignedAtAsc(creator.id!!)
|
||||
val startDate = LocalDateTime.of(2026, 2, 20, 0, 0, 0)
|
||||
val endDate = LocalDateTime.of(2026, 2, 20, 23, 59, 59)
|
||||
val firstAgentLiveRows = repository.getCalculateLiveByCreator(startDate, endDate, firstAgent.id!!)
|
||||
val secondAgentLiveRows = repository.getCalculateLiveByCreator(startDate, endDate, secondAgent.id!!)
|
||||
|
||||
assertEquals(listOf(savedRelations[0].id), firstAgentLiveRows.map { it.assignmentId })
|
||||
assertEquals(listOf(savedRelations[1].id), secondAgentLiveRows.map { it.assignmentId })
|
||||
|
||||
val firstAgentLive = service.getCalculateLiveByCreator("2026-02-20", "2026-02-20", firstAgent.id!!, 0, 10)
|
||||
val secondAgentLive = service.getCalculateLiveByCreator("2026-02-20", "2026-02-20", secondAgent.id!!, 0, 10)
|
||||
assertGenericSettlementResponse(firstAgentLive, expectedCount = 1, expectedTotalCan = 10)
|
||||
assertGenericSettlementResponse(secondAgentLive, expectedCount = 1, expectedTotalCan = 20)
|
||||
|
||||
val firstAgentContent = service.getCalculateContentByCreator("2026-02-20", "2026-02-20", firstAgent.id!!, 0, 10)
|
||||
val secondAgentContent = service.getCalculateContentByCreator("2026-02-20", "2026-02-20", secondAgent.id!!, 0, 10)
|
||||
assertGenericSettlementResponse(firstAgentContent, expectedCount = 1, expectedTotalCan = 10)
|
||||
assertGenericSettlementResponse(secondAgentContent, expectedCount = 1, expectedTotalCan = 20)
|
||||
|
||||
val firstAgentCommunity = service.getCalculateCommunityByCreator("2026-02-20", "2026-02-20", firstAgent.id!!, 0, 10)
|
||||
val secondAgentCommunity = service.getCalculateCommunityByCreator("2026-02-20", "2026-02-20", secondAgent.id!!, 0, 10)
|
||||
assertGenericSettlementResponse(firstAgentCommunity, expectedCount = 1, expectedTotalCan = 10)
|
||||
assertGenericSettlementResponse(secondAgentCommunity, expectedCount = 1, expectedTotalCan = 20)
|
||||
|
||||
val firstAgentContentDonation = service.getCalculateContentDonationByCreator(
|
||||
"2026-02-20",
|
||||
"2026-02-20",
|
||||
firstAgent.id!!,
|
||||
0,
|
||||
10
|
||||
)
|
||||
val secondAgentContentDonation = service.getCalculateContentDonationByCreator(
|
||||
"2026-02-20",
|
||||
"2026-02-20",
|
||||
secondAgent.id!!,
|
||||
0,
|
||||
10
|
||||
)
|
||||
assertGenericSettlementResponse(firstAgentContentDonation, expectedCount = 1, expectedTotalCan = 10)
|
||||
assertGenericSettlementResponse(secondAgentContentDonation, expectedCount = 1, expectedTotalCan = 20)
|
||||
|
||||
val firstAgentChannelDonation = service.getChannelDonationByCreator("2026-02-20", "2026-02-20", firstAgent.id!!, 0, 10)
|
||||
val secondAgentChannelDonation = service.getChannelDonationByCreator("2026-02-20", "2026-02-20", secondAgent.id!!, 0, 10)
|
||||
assertChannelDonationSettlementResponse(firstAgentChannelDonation, expectedCount = 1, expectedTotalCan = 10)
|
||||
assertChannelDonationSettlementResponse(secondAgentChannelDonation, expectedCount = 1, expectedTotalCan = 20)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("정산 조회는 기간 중 agent 비율 변경이 있어도 거래 시점 기준 비율로 agent 정산금을 계산한다")
|
||||
fun shouldResolveAgentSettlementRatioAtEventTimeAcrossAllSettlementCategories() {
|
||||
val agent = saveMember("agent-ratio-history", MemberRole.AGENT)
|
||||
val creator = saveMember("creator-ratio-history", MemberRole.CREATOR)
|
||||
val buyer = saveMember("buyer-ratio-history", MemberRole.USER)
|
||||
|
||||
saveRelation(
|
||||
agent = agent,
|
||||
creator = creator,
|
||||
assignedAt = LocalDateTime.of(2026, 2, 1, 0, 0, 0),
|
||||
unassignedAt = null
|
||||
)
|
||||
saveAgentSettlementRatio(agent, settlementRatio = 10, effectiveFrom = LocalDateTime.of(2026, 2, 1, 0, 0, 0))
|
||||
saveAgentSettlementRatio(
|
||||
agent,
|
||||
settlementRatio = 20,
|
||||
effectiveFrom = LocalDateTime.of(2026, 2, 20, 12, 0, 0),
|
||||
effectiveTo = null,
|
||||
previousEffectiveTo = LocalDateTime.of(2026, 2, 20, 12, 0, 0)
|
||||
)
|
||||
saveCreatorSettlementRatio(creator, live = 70, content = 70, community = 70)
|
||||
|
||||
val room = saveLiveRoom(creator)
|
||||
saveLiveUseCan(buyer, room, 10, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||
saveLiveUseCan(buyer, room, 20, LocalDateTime.of(2026, 2, 20, 14, 0, 0))
|
||||
|
||||
val contentBefore = saveAudioContent(creator, "ratio-content-before", price = 10, settlementRatio = null)
|
||||
val contentAfter = saveAudioContent(creator, "ratio-content-after", price = 20, settlementRatio = null)
|
||||
saveOrder(buyer, creator, contentBefore, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||
saveOrder(buyer, creator, contentAfter, LocalDateTime.of(2026, 2, 20, 14, 0, 0))
|
||||
|
||||
val communityBefore = saveCommunityPost(creator, 10)
|
||||
val communityAfter = saveCommunityPost(creator, 20)
|
||||
saveCommunityUseCan(buyer, communityBefore, 10, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||
saveCommunityUseCan(buyer, communityAfter, 20, LocalDateTime.of(2026, 2, 20, 14, 0, 0))
|
||||
|
||||
saveContentDonationUseCan(buyer, contentBefore, 10, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||
saveContentDonationUseCan(buyer, contentAfter, 20, LocalDateTime.of(2026, 2, 20, 14, 0, 0))
|
||||
|
||||
val channelBefore = saveChannelDonationUseCan(buyer, 10, LocalDateTime.of(2026, 2, 20, 10, 0, 0))
|
||||
saveUseCanCalculate(channelBefore, creator.id!!, 10, PaymentGateway.PG)
|
||||
val channelAfter = saveChannelDonationUseCan(buyer, 20, LocalDateTime.of(2026, 2, 20, 14, 0, 0))
|
||||
saveUseCanCalculate(channelAfter, creator.id!!, 20, PaymentGateway.PG)
|
||||
|
||||
val savedRatios = agentSettlementRatioRepository.findAllByMemberIdOrderByEffectiveFromAsc(agent.id!!)
|
||||
val startDate = LocalDateTime.of(2026, 2, 20, 0, 0, 0)
|
||||
val endDate = LocalDateTime.of(2026, 2, 20, 23, 59, 59)
|
||||
val liveRows = repository.getCalculateLiveByCreator(startDate, endDate, agent.id!!)
|
||||
assertEquals(savedRatios.mapNotNull { it.id }, liveRows.mapNotNull { it.agentSettlementRatioId }.sorted())
|
||||
|
||||
val expectedGenericAgentSettlementAmount =
|
||||
calculateGenericAgentSettlementAmount(totalCan = 10, settlementRatio = 70, agentSettlementRatio = 10) +
|
||||
calculateGenericAgentSettlementAmount(totalCan = 20, settlementRatio = 70, agentSettlementRatio = 20)
|
||||
val expectedChannelAgentSettlementAmount =
|
||||
calculateChannelAgentSettlementAmount(totalCan = 10, agentSettlementRatio = 10) +
|
||||
calculateChannelAgentSettlementAmount(totalCan = 20, agentSettlementRatio = 20)
|
||||
|
||||
val liveResponse = service.getCalculateLiveByCreator("2026-02-20", "2026-02-20", agent.id!!, 0, 10)
|
||||
assertGenericSettlementResponse(
|
||||
liveResponse,
|
||||
expectedCount = 2,
|
||||
expectedTotalCan = 30,
|
||||
expectedAgentSettlementAmount = expectedGenericAgentSettlementAmount
|
||||
)
|
||||
|
||||
val contentResponse = service.getCalculateContentByCreator("2026-02-20", "2026-02-20", agent.id!!, 0, 10)
|
||||
assertGenericSettlementResponse(
|
||||
contentResponse,
|
||||
expectedCount = 2,
|
||||
expectedTotalCan = 30,
|
||||
expectedAgentSettlementAmount = expectedGenericAgentSettlementAmount
|
||||
)
|
||||
|
||||
val communityResponse = service.getCalculateCommunityByCreator("2026-02-20", "2026-02-20", agent.id!!, 0, 10)
|
||||
assertGenericSettlementResponse(
|
||||
communityResponse,
|
||||
expectedCount = 2,
|
||||
expectedTotalCan = 30,
|
||||
expectedAgentSettlementAmount = expectedGenericAgentSettlementAmount
|
||||
)
|
||||
|
||||
val contentDonationResponse = service.getCalculateContentDonationByCreator("2026-02-20", "2026-02-20", agent.id!!, 0, 10)
|
||||
assertGenericSettlementResponse(
|
||||
contentDonationResponse,
|
||||
expectedCount = 2,
|
||||
expectedTotalCan = 30,
|
||||
expectedAgentSettlementAmount = expectedGenericAgentSettlementAmount
|
||||
)
|
||||
|
||||
val channelDonationResponse = service.getChannelDonationByCreator("2026-02-20", "2026-02-20", agent.id!!, 0, 10)
|
||||
assertChannelDonationSettlementResponse(
|
||||
channelDonationResponse,
|
||||
expectedCount = 2,
|
||||
expectedTotalCan = 30,
|
||||
expectedAgentSettlementAmount = expectedChannelAgentSettlementAmount
|
||||
)
|
||||
}
|
||||
|
||||
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 = LocalDateTime.of(2026, 2, 1, 0, 0, 0),
|
||||
unassignedAt: LocalDateTime? = null
|
||||
) {
|
||||
val relation = AgentCreatorRelation()
|
||||
relation.agent = agent
|
||||
relation.creator = creator
|
||||
relation.assignedAt = assignedAt
|
||||
relation.unassignedAt = unassignedAt
|
||||
relationRepository.saveAndFlush(relation)
|
||||
}
|
||||
|
||||
private fun saveAgentSettlementRatio(
|
||||
agent: Member,
|
||||
settlementRatio: Int,
|
||||
effectiveFrom: LocalDateTime,
|
||||
effectiveTo: LocalDateTime? = null,
|
||||
previousEffectiveTo: LocalDateTime? = null
|
||||
) {
|
||||
if (previousEffectiveTo != null) {
|
||||
val previous = agentSettlementRatioRepository.findFirstByMemberIdAndEffectiveToIsNull(agent.id!!)
|
||||
previous?.effectiveTo = previousEffectiveTo
|
||||
previous?.let { agentSettlementRatioRepository.saveAndFlush(it) }
|
||||
}
|
||||
|
||||
val ratio = AgentSettlementRatio(
|
||||
settlementRatio = settlementRatio,
|
||||
effectiveFrom = effectiveFrom
|
||||
)
|
||||
ratio.member = agent
|
||||
ratio.effectiveTo = effectiveTo
|
||||
agentSettlementRatioRepository.saveAndFlush(ratio)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
private fun assertGenericSettlementResponse(
|
||||
response: GetAgentSettlementByCreatorResponse,
|
||||
expectedCount: Int,
|
||||
expectedTotalCan: Int,
|
||||
expectedAgentSettlementAmount: Int? = null
|
||||
) {
|
||||
assertEquals(1, response.totalCount)
|
||||
assertEquals(1, response.items.size)
|
||||
assertEquals(expectedCount, response.total.count)
|
||||
assertEquals(expectedCount, response.items[0].count)
|
||||
assertEquals(expectedTotalCan, response.total.totalCan)
|
||||
assertEquals(expectedTotalCan, response.items[0].totalCan)
|
||||
expectedAgentSettlementAmount?.let {
|
||||
assertEquals(it, response.total.agentSettlementAmount)
|
||||
assertEquals(it, response.items[0].agentSettlementAmount)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertChannelDonationSettlementResponse(
|
||||
response: GetAgentChannelDonationSettlementByCreatorResponse,
|
||||
expectedCount: Int,
|
||||
expectedTotalCan: Int,
|
||||
expectedAgentSettlementAmount: Int? = null
|
||||
) {
|
||||
assertEquals(1, response.totalCount)
|
||||
assertEquals(1, response.items.size)
|
||||
assertEquals(expectedCount, response.total.count)
|
||||
assertEquals(expectedCount, response.items[0].count)
|
||||
assertEquals(expectedTotalCan, response.total.totalCan)
|
||||
assertEquals(expectedTotalCan, response.items[0].totalCan)
|
||||
expectedAgentSettlementAmount?.let {
|
||||
assertEquals(it, response.total.agentSettlementAmount)
|
||||
assertEquals(it, response.items[0].agentSettlementAmount)
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateGenericAgentSettlementAmount(totalCan: Int, settlementRatio: Int, agentSettlementRatio: Int): Int {
|
||||
val totalKrw = java.math.BigDecimal(totalCan).multiply(java.math.BigDecimal("100"))
|
||||
val fee = totalKrw.multiply(java.math.BigDecimal("0.066"))
|
||||
val settlementAmount = totalKrw.subtract(fee)
|
||||
.multiply(java.math.BigDecimal(settlementRatio).divide(java.math.BigDecimal("100")))
|
||||
.setScale(0, java.math.RoundingMode.HALF_UP)
|
||||
.toInt()
|
||||
|
||||
return java.math.BigDecimal(settlementAmount)
|
||||
.multiply(java.math.BigDecimal(agentSettlementRatio).divide(java.math.BigDecimal("100")))
|
||||
.setScale(0, java.math.RoundingMode.HALF_UP)
|
||||
.toInt()
|
||||
}
|
||||
|
||||
private fun calculateChannelAgentSettlementAmount(totalCan: Int, agentSettlementRatio: Int): Int {
|
||||
val settlementAmount = kr.co.vividnext.sodalive.calculate.channelDonation.ChannelDonationSettlementCalculator
|
||||
.calculate(totalCan)
|
||||
.settlementAmount
|
||||
|
||||
return java.math.BigDecimal(settlementAmount)
|
||||
.multiply(java.math.BigDecimal(agentSettlementRatio).divide(java.math.BigDecimal("100")))
|
||||
.setScale(0, java.math.RoundingMode.HALF_UP)
|
||||
.toInt()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,633 @@
|
||||
package kr.co.vividnext.sodalive.partner.agent.calculate
|
||||
|
||||
import kr.co.vividnext.sodalive.extensions.convertLocalDateTime
|
||||
import kr.co.vividnext.sodalive.partner.agent.settlement.snapshot.AgentSettlementSnapshot
|
||||
import kr.co.vividnext.sodalive.partner.agent.settlement.snapshot.AgentSettlementSnapshotRepository
|
||||
import kr.co.vividnext.sodalive.partner.agent.settlement.snapshot.AgentSettlementSnapshotType
|
||||
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 java.time.LocalDateTime
|
||||
|
||||
class AgentCalculateServiceTest {
|
||||
private lateinit var repository: AgentCalculateQueryRepository
|
||||
private lateinit var snapshotRepository: AgentSettlementSnapshotRepository
|
||||
private lateinit var service: AgentCalculateService
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
repository = Mockito.mock(AgentCalculateQueryRepository::class.java)
|
||||
snapshotRepository = Mockito.mock(
|
||||
AgentSettlementSnapshotRepository::class.java,
|
||||
Mockito.withSettings().defaultAnswer { invocation ->
|
||||
if (invocation.method.name == "findAllByPeriodStartAndPeriodEndAndSettlementTypeAndAgentIdOrderByCreatorIdDesc") {
|
||||
emptyList<AgentSettlementSnapshot>()
|
||||
} else {
|
||||
Mockito.RETURNS_DEFAULTS.answer(invocation)
|
||||
}
|
||||
}
|
||||
)
|
||||
service = AgentCalculateService(
|
||||
repository = repository,
|
||||
snapshotRepository = snapshotRepository
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("에이전트 서비스는 소속 크리에이터 목록을 페이지 조건으로 조회한다")
|
||||
fun shouldGetAssignedCreators() {
|
||||
val items = listOf(
|
||||
GetAgentAssignedCreatorItem(
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a"
|
||||
)
|
||||
)
|
||||
|
||||
Mockito.`when`(
|
||||
repository.getAssignedCreatorTotalCount(
|
||||
Mockito.eq(7L),
|
||||
Mockito.any(LocalDateTime::class.java) ?: LocalDateTime.now()
|
||||
)
|
||||
).thenReturn(1)
|
||||
Mockito.`when`(
|
||||
repository.getAssignedCreators(
|
||||
Mockito.eq(7L),
|
||||
Mockito.eq(10L),
|
||||
Mockito.eq(5L),
|
||||
Mockito.any(LocalDateTime::class.java) ?: LocalDateTime.now()
|
||||
)
|
||||
).thenReturn(items)
|
||||
|
||||
val response = service.getAssignedCreators(agentId = 7L, offset = 10L, limit = 5L)
|
||||
|
||||
assertEquals(1, response.totalCount)
|
||||
assertEquals(21L, response.items[0].creatorId)
|
||||
Mockito.verify(repository).getAssignedCreatorTotalCount(
|
||||
Mockito.eq(7L),
|
||||
Mockito.any(LocalDateTime::class.java) ?: LocalDateTime.now()
|
||||
)
|
||||
Mockito.verify(
|
||||
repository
|
||||
).getAssignedCreators(
|
||||
Mockito.eq(7L),
|
||||
Mockito.eq(10L),
|
||||
Mockito.eq(5L),
|
||||
Mockito.any(LocalDateTime::class.java) ?: LocalDateTime.now()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("에이전트 서비스는 라이브 크리에이터별 응답과 합계에 agent 정산금을 계산한다")
|
||||
fun shouldBuildLiveSummaryResponse() {
|
||||
val queryData = listOf(
|
||||
GetAgentCreatorSettlementSummaryQueryData(
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a",
|
||||
count = 2L,
|
||||
totalCan = 100,
|
||||
settlementRatio = 70,
|
||||
agentSettlementRatio = 10
|
||||
)
|
||||
)
|
||||
|
||||
Mockito.`when`(
|
||||
repository.getCalculateLiveByCreatorTotalCount(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L
|
||||
)
|
||||
).thenReturn(1)
|
||||
Mockito.`when`(
|
||||
repository.getCalculateLiveByCreator(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L
|
||||
)
|
||||
).thenReturn(queryData)
|
||||
Mockito.`when`(
|
||||
repository.getCalculateLiveByCreator(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 20L
|
||||
)
|
||||
).thenReturn(queryData)
|
||||
|
||||
val response = service.getCalculateLiveByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 20L
|
||||
)
|
||||
|
||||
assertEquals(1, response.totalCount)
|
||||
assertEquals(2, response.total.count)
|
||||
assertEquals(10_000, response.total.krw)
|
||||
assertEquals(6_538, response.total.settlementAmount)
|
||||
assertEquals(654, response.total.agentSettlementAmount)
|
||||
assertEquals(21L, response.items[0].creatorId)
|
||||
assertEquals(654, response.items[0].agentSettlementAmount)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("에이전트 서비스는 콘텐츠 크리에이터별 응답을 크리에이터 기준으로 병합하고 agent 정산금을 계산한다")
|
||||
fun shouldMergeContentSummaryRowsPerCreator() {
|
||||
val totalRows = listOf(
|
||||
GetAgentCreatorSettlementSummaryQueryData(
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a",
|
||||
count = 1L,
|
||||
totalCan = 50,
|
||||
settlementRatio = 80,
|
||||
agentSettlementRatio = 10
|
||||
),
|
||||
GetAgentCreatorSettlementSummaryQueryData(
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a",
|
||||
count = 2L,
|
||||
totalCan = 30,
|
||||
settlementRatio = 60,
|
||||
agentSettlementRatio = 20
|
||||
)
|
||||
)
|
||||
|
||||
Mockito.`when`(
|
||||
repository.getCalculateContentByCreatorTotalCount(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L
|
||||
)
|
||||
).thenReturn(1)
|
||||
Mockito.`when`(
|
||||
repository.getCalculateContentByCreator(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L
|
||||
)
|
||||
).thenReturn(totalRows)
|
||||
Mockito.`when`(
|
||||
repository.getCalculateContentByCreator(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 20L
|
||||
)
|
||||
).thenReturn(totalRows)
|
||||
|
||||
val response = service.getCalculateContentByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 20L
|
||||
)
|
||||
|
||||
assertEquals(1, response.totalCount)
|
||||
assertEquals(3, response.total.count)
|
||||
assertEquals(80, response.total.totalCan)
|
||||
assertEquals(5_417, response.total.settlementAmount)
|
||||
assertEquals(710, response.total.agentSettlementAmount)
|
||||
assertEquals(3, response.items[0].count)
|
||||
assertEquals(80, response.items[0].totalCan)
|
||||
assertEquals(710, response.items[0].agentSettlementAmount)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("에이전트 서비스는 커뮤니티 크리에이터별 응답과 합계에 agent 정산금을 계산한다")
|
||||
fun shouldBuildCommunitySummaryResponse() {
|
||||
val queryData = listOf(
|
||||
GetAgentCreatorSettlementSummaryQueryData(
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a",
|
||||
count = 2L,
|
||||
totalCan = 30,
|
||||
settlementRatio = 60,
|
||||
agentSettlementRatio = 10
|
||||
)
|
||||
)
|
||||
|
||||
Mockito.`when`(
|
||||
repository.getCalculateCommunityByCreatorTotalCount(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L
|
||||
)
|
||||
).thenReturn(1)
|
||||
Mockito.`when`(
|
||||
repository.getCalculateCommunityByCreator(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L
|
||||
)
|
||||
).thenReturn(queryData)
|
||||
Mockito.`when`(
|
||||
repository.getCalculateCommunityByCreator(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 20L
|
||||
)
|
||||
).thenReturn(queryData)
|
||||
|
||||
val response = service.getCalculateCommunityByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 20L
|
||||
)
|
||||
|
||||
assertEquals(1, response.totalCount)
|
||||
assertEquals(1_681, response.total.settlementAmount)
|
||||
assertEquals(168, response.total.agentSettlementAmount)
|
||||
assertEquals(168, response.items[0].agentSettlementAmount)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("에이전트 서비스는 콘텐츠후원 크리에이터별 응답과 합계에 agent 정산금을 계산한다")
|
||||
fun shouldBuildContentDonationSummaryResponse() {
|
||||
val queryData = listOf(
|
||||
GetAgentCreatorSettlementSummaryQueryData(
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a",
|
||||
count = 2L,
|
||||
totalCan = 20,
|
||||
settlementRatio = null,
|
||||
agentSettlementRatio = 10
|
||||
)
|
||||
)
|
||||
|
||||
Mockito.`when`(
|
||||
repository.getCalculateContentDonationByCreatorTotalCount(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L
|
||||
)
|
||||
).thenReturn(1)
|
||||
Mockito.`when`(
|
||||
repository.getCalculateContentDonationByCreator(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L
|
||||
)
|
||||
).thenReturn(queryData)
|
||||
Mockito.`when`(
|
||||
repository.getCalculateContentDonationByCreator(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 20L
|
||||
)
|
||||
).thenReturn(queryData)
|
||||
|
||||
val response = service.getCalculateContentDonationByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 20L
|
||||
)
|
||||
|
||||
assertEquals(1, response.totalCount)
|
||||
assertEquals(1_308, response.total.settlementAmount)
|
||||
assertEquals(131, response.total.agentSettlementAmount)
|
||||
assertEquals(131, response.items[0].agentSettlementAmount)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("에이전트 비율 이력이 없으면 일반 정산 응답은 10퍼센트 기본값으로 agent 정산금을 계산한다")
|
||||
fun shouldApplyDefaultAgentSettlementRatioWhenAgentRatioHistoryDoesNotExist() {
|
||||
val queryData = listOf(
|
||||
GetAgentCreatorSettlementSummaryQueryData(
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a",
|
||||
count = 2L,
|
||||
totalCan = 20,
|
||||
settlementRatio = null,
|
||||
agentSettlementRatio = null
|
||||
)
|
||||
)
|
||||
|
||||
Mockito.`when`(
|
||||
repository.getCalculateContentDonationByCreatorTotalCount(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L
|
||||
)
|
||||
).thenReturn(1)
|
||||
Mockito.`when`(
|
||||
repository.getCalculateContentDonationByCreator(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L
|
||||
)
|
||||
).thenReturn(queryData)
|
||||
Mockito.`when`(
|
||||
repository.getCalculateContentDonationByCreator(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 20L
|
||||
)
|
||||
).thenReturn(queryData)
|
||||
|
||||
val response = service.getCalculateContentDonationByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 20L
|
||||
)
|
||||
|
||||
assertEquals(1, response.totalCount)
|
||||
assertEquals(1_308, response.total.settlementAmount)
|
||||
assertEquals(131, response.total.agentSettlementAmount)
|
||||
assertEquals(131, response.items[0].agentSettlementAmount)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("에이전트 서비스는 채널후원 크리에이터별 응답과 합계에 agent 정산금을 계산한다")
|
||||
fun shouldBuildChannelDonationSummaryResponse() {
|
||||
val queryData = listOf(
|
||||
GetAgentChannelDonationSettlementByCreatorQueryData(
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a",
|
||||
count = 1L,
|
||||
totalCan = 50,
|
||||
agentSettlementRatio = 10
|
||||
)
|
||||
)
|
||||
|
||||
Mockito.`when`(
|
||||
repository.getChannelDonationByCreatorTotalCount(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L
|
||||
)
|
||||
).thenReturn(1)
|
||||
Mockito.`when`(
|
||||
repository.getChannelDonationByCreator(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L
|
||||
)
|
||||
).thenReturn(queryData)
|
||||
Mockito.`when`(
|
||||
repository.getChannelDonationByCreator(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 20L
|
||||
)
|
||||
).thenReturn(queryData)
|
||||
|
||||
val response = service.getChannelDonationByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 20L
|
||||
)
|
||||
|
||||
assertEquals(1, response.totalCount)
|
||||
assertEquals(3_970, response.total.settlementAmount)
|
||||
assertEquals(397, response.total.agentSettlementAmount)
|
||||
assertEquals(397, response.items[0].agentSettlementAmount)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("에이전트 비율 이력이 없으면 채널후원 응답은 10퍼센트 기본값으로 agent 정산금을 계산한다")
|
||||
fun shouldApplyDefaultAgentSettlementRatioToChannelDonationWhenAgentRatioHistoryDoesNotExist() {
|
||||
val queryData = listOf(
|
||||
GetAgentChannelDonationSettlementByCreatorQueryData(
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a",
|
||||
count = 1L,
|
||||
totalCan = 50,
|
||||
agentSettlementRatio = null
|
||||
)
|
||||
)
|
||||
|
||||
Mockito.`when`(
|
||||
repository.getChannelDonationByCreatorTotalCount(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L
|
||||
)
|
||||
).thenReturn(1)
|
||||
Mockito.`when`(
|
||||
repository.getChannelDonationByCreator(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L
|
||||
)
|
||||
).thenReturn(queryData)
|
||||
Mockito.`when`(
|
||||
repository.getChannelDonationByCreator(
|
||||
startDate = "2026-02-20".convertLocalDateTime(),
|
||||
endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 20L
|
||||
)
|
||||
).thenReturn(queryData)
|
||||
|
||||
val response = service.getChannelDonationByCreator(
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21",
|
||||
agentId = 7L,
|
||||
offset = 0L,
|
||||
limit = 20L
|
||||
)
|
||||
|
||||
assertEquals(1, response.totalCount)
|
||||
assertEquals(3_970, response.total.settlementAmount)
|
||||
assertEquals(397, response.total.agentSettlementAmount)
|
||||
assertEquals(397, response.items[0].agentSettlementAmount)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("에이전트 서비스는 finalized 기간이면 다섯 카테고리 모두 스냅샷을 우선 사용한다")
|
||||
fun shouldUseFinalizedSnapshotsFirstAcrossAllSettlementCategories() {
|
||||
val startDate = "2026-02-20".convertLocalDateTime()
|
||||
val endDate = "2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59)
|
||||
|
||||
Mockito.`when`(
|
||||
snapshotRepository.findAllByPeriodStartAndPeriodEndAndSettlementTypeAndAgentIdOrderByCreatorIdDesc(
|
||||
startDate,
|
||||
endDate,
|
||||
AgentSettlementSnapshotType.LIVE,
|
||||
7L
|
||||
)
|
||||
).thenReturn(
|
||||
listOf(
|
||||
createSnapshot(
|
||||
settlementType = AgentSettlementSnapshotType.LIVE,
|
||||
count = 2,
|
||||
totalCan = 100,
|
||||
krw = 10_000,
|
||||
fee = 660,
|
||||
settlementAmount = 6_538,
|
||||
tax = 216,
|
||||
depositAmount = 6_322,
|
||||
agentSettlementAmount = 654,
|
||||
appliedAgentSettlementRatio = 10
|
||||
)
|
||||
)
|
||||
)
|
||||
Mockito.`when`(
|
||||
snapshotRepository.findAllByPeriodStartAndPeriodEndAndSettlementTypeAndAgentIdOrderByCreatorIdDesc(
|
||||
startDate,
|
||||
endDate,
|
||||
AgentSettlementSnapshotType.CONTENT,
|
||||
7L
|
||||
)
|
||||
).thenReturn(
|
||||
listOf(
|
||||
createSnapshot(
|
||||
settlementType = AgentSettlementSnapshotType.CONTENT,
|
||||
count = 3,
|
||||
totalCan = 80,
|
||||
krw = 8_000,
|
||||
fee = 528,
|
||||
settlementAmount = 5_417,
|
||||
tax = 179,
|
||||
depositAmount = 5_238,
|
||||
agentSettlementAmount = 710,
|
||||
appliedAgentSettlementRatio = null
|
||||
)
|
||||
)
|
||||
)
|
||||
Mockito.`when`(
|
||||
snapshotRepository.findAllByPeriodStartAndPeriodEndAndSettlementTypeAndAgentIdOrderByCreatorIdDesc(
|
||||
startDate,
|
||||
endDate,
|
||||
AgentSettlementSnapshotType.COMMUNITY,
|
||||
7L
|
||||
)
|
||||
).thenReturn(
|
||||
listOf(
|
||||
createSnapshot(
|
||||
settlementType = AgentSettlementSnapshotType.COMMUNITY,
|
||||
count = 2,
|
||||
totalCan = 30,
|
||||
krw = 3_000,
|
||||
fee = 198,
|
||||
settlementAmount = 1_681,
|
||||
tax = 55,
|
||||
depositAmount = 1_626,
|
||||
agentSettlementAmount = 168,
|
||||
appliedAgentSettlementRatio = 10
|
||||
)
|
||||
)
|
||||
)
|
||||
Mockito.`when`(
|
||||
snapshotRepository.findAllByPeriodStartAndPeriodEndAndSettlementTypeAndAgentIdOrderByCreatorIdDesc(
|
||||
startDate,
|
||||
endDate,
|
||||
AgentSettlementSnapshotType.CONTENT_DONATION,
|
||||
7L
|
||||
)
|
||||
).thenReturn(
|
||||
listOf(
|
||||
createSnapshot(
|
||||
settlementType = AgentSettlementSnapshotType.CONTENT_DONATION,
|
||||
count = 2,
|
||||
totalCan = 20,
|
||||
krw = 2_000,
|
||||
fee = 132,
|
||||
settlementAmount = 1_308,
|
||||
tax = 43,
|
||||
depositAmount = 1_265,
|
||||
agentSettlementAmount = 131,
|
||||
appliedAgentSettlementRatio = 10
|
||||
)
|
||||
)
|
||||
)
|
||||
Mockito.`when`(
|
||||
snapshotRepository.findAllByPeriodStartAndPeriodEndAndSettlementTypeAndAgentIdOrderByCreatorIdDesc(
|
||||
startDate,
|
||||
endDate,
|
||||
AgentSettlementSnapshotType.CHANNEL_DONATION,
|
||||
7L
|
||||
)
|
||||
).thenReturn(
|
||||
listOf(
|
||||
createSnapshot(
|
||||
settlementType = AgentSettlementSnapshotType.CHANNEL_DONATION,
|
||||
count = 1,
|
||||
totalCan = 50,
|
||||
krw = 5_000,
|
||||
fee = 330,
|
||||
settlementAmount = 3_970,
|
||||
tax = 131,
|
||||
depositAmount = 3_839,
|
||||
agentSettlementAmount = 397,
|
||||
appliedAgentSettlementRatio = 10
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val liveResponse = service.getCalculateLiveByCreator("2026-02-20", "2026-02-21", 7L, 0L, 20L)
|
||||
val contentResponse = service.getCalculateContentByCreator("2026-02-20", "2026-02-21", 7L, 0L, 20L)
|
||||
val communityResponse = service.getCalculateCommunityByCreator("2026-02-20", "2026-02-21", 7L, 0L, 20L)
|
||||
val contentDonationResponse = service.getCalculateContentDonationByCreator("2026-02-20", "2026-02-21", 7L, 0L, 20L)
|
||||
val channelDonationResponse = service.getChannelDonationByCreator("2026-02-20", "2026-02-21", 7L, 0L, 20L)
|
||||
|
||||
assertEquals(654, liveResponse.total.agentSettlementAmount)
|
||||
assertEquals(710, contentResponse.total.agentSettlementAmount)
|
||||
assertEquals(168, communityResponse.total.agentSettlementAmount)
|
||||
assertEquals(131, contentDonationResponse.total.agentSettlementAmount)
|
||||
assertEquals(397, channelDonationResponse.total.agentSettlementAmount)
|
||||
assertEquals(6_538, liveResponse.items[0].settlementAmount)
|
||||
assertEquals(5_417, contentResponse.items[0].settlementAmount)
|
||||
assertEquals(1_681, communityResponse.items[0].settlementAmount)
|
||||
assertEquals(1_308, contentDonationResponse.items[0].settlementAmount)
|
||||
assertEquals(3_970, channelDonationResponse.items[0].settlementAmount)
|
||||
assertEquals(131, channelDonationResponse.items[0].withholdingTax)
|
||||
Mockito.verifyNoInteractions(repository)
|
||||
}
|
||||
|
||||
private fun createSnapshot(
|
||||
settlementType: AgentSettlementSnapshotType,
|
||||
count: Int,
|
||||
totalCan: Int,
|
||||
krw: Int,
|
||||
fee: Int,
|
||||
settlementAmount: Int,
|
||||
tax: Int,
|
||||
depositAmount: Int,
|
||||
agentSettlementAmount: Int,
|
||||
appliedAgentSettlementRatio: Int?
|
||||
): AgentSettlementSnapshot {
|
||||
return AgentSettlementSnapshot(
|
||||
periodStart = LocalDateTime.of(2026, 2, 20, 0, 0, 0),
|
||||
periodEnd = LocalDateTime.of(2026, 2, 21, 23, 59, 59),
|
||||
settlementType = settlementType,
|
||||
agentId = 7L,
|
||||
agentNickname = "agent-a",
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a",
|
||||
appliedAgentSettlementRatio = appliedAgentSettlementRatio,
|
||||
count = count,
|
||||
totalCan = totalCan,
|
||||
krw = krw,
|
||||
fee = fee,
|
||||
settlementAmount = settlementAmount,
|
||||
tax = tax,
|
||||
depositAmount = depositAmount,
|
||||
agentSettlementAmount = agentSettlementAmount,
|
||||
finalizedAt = LocalDateTime.of(2026, 2, 22, 0, 0, 0),
|
||||
finalizedByMemberId = 1L
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user