feat(agent-settlement): 에이전트 정산 스냅샷 관리 기능을 추가한다
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
package kr.co.vividnext.sodalive.admin.partner.agent.settlement
|
||||
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.partner.agent.settlement.snapshot.AgentSettlementSnapshotType
|
||||
import kr.co.vividnext.sodalive.partner.agent.settlement.snapshot.FinalizeAgentSettlementSnapshotRequest
|
||||
import kr.co.vividnext.sodalive.partner.agent.settlement.snapshot.FinalizeAgentSettlementSnapshotResponse
|
||||
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
|
||||
|
||||
class AdminAgentSettlementSnapshotControllerTest {
|
||||
private lateinit var service: AdminAgentSettlementSnapshotService
|
||||
private lateinit var controller: AdminAgentSettlementSnapshotController
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
service = Mockito.mock(AdminAgentSettlementSnapshotService::class.java)
|
||||
controller = AdminAgentSettlementSnapshotController(service)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("인증 사용자 정보가 없으면 관리자 확정 정산 요청은 예외를 던진다")
|
||||
fun shouldThrowWhenMemberIsNull() {
|
||||
val request = FinalizeAgentSettlementSnapshotRequest(
|
||||
agentId = 7L,
|
||||
settlementType = AgentSettlementSnapshotType.LIVE,
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21"
|
||||
)
|
||||
|
||||
val exception = assertThrows(SodaException::class.java) {
|
||||
controller.finalizeSettlement(request = request, member = null)
|
||||
}
|
||||
|
||||
assertEquals("common.error.bad_credentials", exception.messageKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("관리자 컨트롤러는 확정 정산 요청과 인증 관리자 정보를 서비스로 전달한다")
|
||||
fun shouldForwardFinalizeRequestToService() {
|
||||
val request = FinalizeAgentSettlementSnapshotRequest(
|
||||
agentId = 7L,
|
||||
settlementType = AgentSettlementSnapshotType.LIVE,
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21"
|
||||
)
|
||||
val member = Member(password = "password", nickname = "admin", role = MemberRole.ADMIN)
|
||||
member.id = 99L
|
||||
val body = FinalizeAgentSettlementSnapshotResponse(
|
||||
finalizedCount = 1,
|
||||
alreadyFinalized = false
|
||||
)
|
||||
|
||||
Mockito.`when`(service.finalizeSnapshots(request, 99L)).thenReturn(body)
|
||||
|
||||
val response = controller.finalizeSettlement(request = request, member = member)
|
||||
|
||||
assertEquals(true, response.success)
|
||||
assertEquals(1, response.data!!.finalizedCount)
|
||||
assertEquals(false, response.data!!.alreadyFinalized)
|
||||
Mockito.verify(service).finalizeSnapshots(request, 99L)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
package kr.co.vividnext.sodalive.admin.partner.agent.settlement
|
||||
|
||||
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.AgentCalculateQueryRepository
|
||||
import kr.co.vividnext.sodalive.partner.agent.calculate.GetAgentCreatorSettlementSummaryQueryData
|
||||
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.AgentSettlementSnapshotSourceDetail
|
||||
import kr.co.vividnext.sodalive.partner.agent.settlement.snapshot.AgentSettlementSnapshotSourceDetailRepository
|
||||
import kr.co.vividnext.sodalive.partner.agent.settlement.snapshot.AgentSettlementSnapshotType
|
||||
import kr.co.vividnext.sodalive.partner.agent.settlement.snapshot.FinalizeAgentSettlementSnapshotRequest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Mockito
|
||||
import java.util.Optional
|
||||
|
||||
class AdminAgentSettlementSnapshotServiceTest {
|
||||
private lateinit var snapshotRepository: AgentSettlementSnapshotRepository
|
||||
private lateinit var sourceDetailRepository: AgentSettlementSnapshotSourceDetailRepository
|
||||
private lateinit var calculateRepository: AgentCalculateQueryRepository
|
||||
private lateinit var memberRepository: MemberRepository
|
||||
private lateinit var service: AdminAgentSettlementSnapshotService
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
snapshotRepository = Mockito.mock(AgentSettlementSnapshotRepository::class.java)
|
||||
sourceDetailRepository = Mockito.mock(AgentSettlementSnapshotSourceDetailRepository::class.java)
|
||||
calculateRepository = Mockito.mock(AgentCalculateQueryRepository::class.java)
|
||||
memberRepository = Mockito.mock(MemberRepository::class.java)
|
||||
service = AdminAgentSettlementSnapshotService(
|
||||
snapshotRepository = snapshotRepository,
|
||||
sourceDetailRepository = sourceDetailRepository,
|
||||
calculateRepository = calculateRepository,
|
||||
memberRepository = memberRepository
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("관리자는 live creator summary를 immutable 스냅샷으로 확정 저장한다")
|
||||
fun shouldCreateImmutableSettlementSnapshots() {
|
||||
val request = FinalizeAgentSettlementSnapshotRequest(
|
||||
agentId = 7L,
|
||||
settlementType = AgentSettlementSnapshotType.LIVE,
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21"
|
||||
)
|
||||
val agent = Member(password = "password", nickname = "agent-a", role = MemberRole.AGENT)
|
||||
agent.id = 7L
|
||||
val (startDate, endDate) = request.toDateRange()
|
||||
|
||||
Mockito.`when`(
|
||||
snapshotRepository.existsByPeriodStartAndPeriodEndAndSettlementTypeAndAgentId(
|
||||
startDate,
|
||||
endDate,
|
||||
AgentSettlementSnapshotType.LIVE,
|
||||
7L
|
||||
)
|
||||
).thenReturn(false)
|
||||
Mockito.`when`(memberRepository.findById(7L)).thenReturn(Optional.of(agent))
|
||||
Mockito.`when`(calculateRepository.getCalculateLiveByCreator(startDate, endDate, 7L)).thenReturn(
|
||||
listOf(
|
||||
GetAgentCreatorSettlementSummaryQueryData(
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a",
|
||||
assignmentId = 101L,
|
||||
agentSettlementRatioId = 202L,
|
||||
count = 2L,
|
||||
totalCan = 100,
|
||||
settlementRatio = 70,
|
||||
agentSettlementRatio = 10
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val response = service.finalizeSnapshots(request, finalizedByMemberId = 99L)
|
||||
|
||||
assertEquals(1, response.finalizedCount)
|
||||
assertEquals(false, response.alreadyFinalized)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val captor = ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<AgentSettlementSnapshot>>
|
||||
Mockito.verify(snapshotRepository).saveAll(captor.capture())
|
||||
|
||||
val snapshot = captor.value.single()
|
||||
assertEquals(startDate, snapshot.periodStart)
|
||||
assertEquals(endDate, snapshot.periodEnd)
|
||||
assertEquals(AgentSettlementSnapshotType.LIVE, snapshot.settlementType)
|
||||
assertEquals(7L, snapshot.agentId)
|
||||
assertEquals("agent-a", snapshot.agentNickname)
|
||||
assertEquals(21L, snapshot.creatorId)
|
||||
assertEquals("creator-a", snapshot.creatorNickname)
|
||||
assertEquals(101L, snapshot.assignmentId)
|
||||
assertEquals(202L, snapshot.agentSettlementRatioId)
|
||||
assertEquals(10, snapshot.appliedAgentSettlementRatio)
|
||||
assertEquals(2, snapshot.count)
|
||||
assertEquals(100, snapshot.totalCan)
|
||||
assertEquals(10_000, snapshot.krw)
|
||||
assertEquals(660, snapshot.fee)
|
||||
assertEquals(6_538, snapshot.settlementAmount)
|
||||
assertEquals(216, snapshot.tax)
|
||||
assertEquals(6_322, snapshot.depositAmount)
|
||||
assertEquals(654, snapshot.agentSettlementAmount)
|
||||
assertEquals(99L, snapshot.finalizedByMemberId)
|
||||
assertNotNull(snapshot.finalizedAt)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val detailCaptor = ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<AgentSettlementSnapshotSourceDetail>>
|
||||
Mockito.verify(sourceDetailRepository).saveAll(detailCaptor.capture())
|
||||
val detail = detailCaptor.value.single()
|
||||
assertEquals(101L, detail.assignmentId)
|
||||
assertEquals(202L, detail.agentSettlementRatioId)
|
||||
assertEquals(10, detail.appliedAgentSettlementRatio)
|
||||
assertEquals(2, detail.count)
|
||||
assertEquals(100, detail.totalCan)
|
||||
assertEquals(10_000, detail.krw)
|
||||
assertEquals(660, detail.fee)
|
||||
assertEquals(6_538, detail.settlementAmount)
|
||||
assertEquals(216, detail.tax)
|
||||
assertEquals(6_322, detail.depositAmount)
|
||||
assertEquals(654, detail.agentSettlementAmount)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("기간 중 source row가 여러 개면 summary FK는 비우고 source detail로 provenance를 남긴다")
|
||||
fun shouldStoreMixedPeriodProvenanceInSourceDetails() {
|
||||
val request = FinalizeAgentSettlementSnapshotRequest(
|
||||
agentId = 7L,
|
||||
settlementType = AgentSettlementSnapshotType.LIVE,
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21"
|
||||
)
|
||||
val agent = Member(password = "password", nickname = "agent-a", role = MemberRole.AGENT)
|
||||
agent.id = 7L
|
||||
val (startDate, endDate) = request.toDateRange()
|
||||
|
||||
Mockito.`when`(
|
||||
snapshotRepository.existsByPeriodStartAndPeriodEndAndSettlementTypeAndAgentId(
|
||||
startDate,
|
||||
endDate,
|
||||
AgentSettlementSnapshotType.LIVE,
|
||||
7L
|
||||
)
|
||||
).thenReturn(false)
|
||||
Mockito.`when`(memberRepository.findById(7L)).thenReturn(Optional.of(agent))
|
||||
Mockito.`when`(calculateRepository.getCalculateLiveByCreator(startDate, endDate, 7L)).thenReturn(
|
||||
listOf(
|
||||
GetAgentCreatorSettlementSummaryQueryData(
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a",
|
||||
assignmentId = 101L,
|
||||
agentSettlementRatioId = 202L,
|
||||
count = 1L,
|
||||
totalCan = 40,
|
||||
settlementRatio = 70,
|
||||
agentSettlementRatio = 10
|
||||
),
|
||||
GetAgentCreatorSettlementSummaryQueryData(
|
||||
creatorId = 21L,
|
||||
creatorNickname = "creator-a",
|
||||
assignmentId = 102L,
|
||||
agentSettlementRatioId = 203L,
|
||||
count = 1L,
|
||||
totalCan = 60,
|
||||
settlementRatio = 70,
|
||||
agentSettlementRatio = 20
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val response = service.finalizeSnapshots(request, finalizedByMemberId = 99L)
|
||||
|
||||
assertEquals(1, response.finalizedCount)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val snapshotCaptor = ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<AgentSettlementSnapshot>>
|
||||
Mockito.verify(snapshotRepository).saveAll(snapshotCaptor.capture())
|
||||
val snapshot = snapshotCaptor.value.single()
|
||||
assertEquals(null, snapshot.assignmentId)
|
||||
assertEquals(null, snapshot.agentSettlementRatioId)
|
||||
assertEquals(null, snapshot.appliedAgentSettlementRatio)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val detailCaptor = ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<AgentSettlementSnapshotSourceDetail>>
|
||||
Mockito.verify(sourceDetailRepository).saveAll(detailCaptor.capture())
|
||||
assertEquals(2, detailCaptor.value.size)
|
||||
assertEquals(listOf(101L, 102L), detailCaptor.value.mapNotNull { it.assignmentId }.sorted())
|
||||
assertEquals(listOf(202L, 203L), detailCaptor.value.mapNotNull { it.agentSettlementRatioId }.sorted())
|
||||
assertEquals(snapshot.count, detailCaptor.value.sumOf { it.count })
|
||||
assertEquals(snapshot.totalCan, detailCaptor.value.sumOf { it.totalCan })
|
||||
assertEquals(snapshot.krw, detailCaptor.value.sumOf { it.krw })
|
||||
assertEquals(snapshot.fee, detailCaptor.value.sumOf { it.fee })
|
||||
assertEquals(snapshot.settlementAmount, detailCaptor.value.sumOf { it.settlementAmount })
|
||||
assertEquals(snapshot.tax, detailCaptor.value.sumOf { it.tax })
|
||||
assertEquals(snapshot.depositAmount, detailCaptor.value.sumOf { it.depositAmount })
|
||||
assertEquals(snapshot.agentSettlementAmount, detailCaptor.value.sumOf { it.agentSettlementAmount })
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("동일 기간과 타입이 이미 확정되었으면 스냅샷을 중복 저장하지 않는다")
|
||||
fun shouldSkipSavingWhenSnapshotsAlreadyExist() {
|
||||
val request = FinalizeAgentSettlementSnapshotRequest(
|
||||
agentId = 7L,
|
||||
settlementType = AgentSettlementSnapshotType.LIVE,
|
||||
startDateStr = "2026-02-20",
|
||||
endDateStr = "2026-02-21"
|
||||
)
|
||||
val (startDate, endDate) = request.toDateRange()
|
||||
|
||||
Mockito.`when`(
|
||||
snapshotRepository.existsByPeriodStartAndPeriodEndAndSettlementTypeAndAgentId(
|
||||
startDate,
|
||||
endDate,
|
||||
AgentSettlementSnapshotType.LIVE,
|
||||
7L
|
||||
)
|
||||
).thenReturn(true)
|
||||
|
||||
val response = service.finalizeSnapshots(request, finalizedByMemberId = 99L)
|
||||
|
||||
assertEquals(0, response.finalizedCount)
|
||||
assertEquals(true, response.alreadyFinalized)
|
||||
Mockito.verify(snapshotRepository).existsByPeriodStartAndPeriodEndAndSettlementTypeAndAgentId(
|
||||
startDate,
|
||||
endDate,
|
||||
AgentSettlementSnapshotType.LIVE,
|
||||
7L
|
||||
)
|
||||
Mockito.verify(snapshotRepository, Mockito.never()).saveAll(Mockito.anyList())
|
||||
Mockito.verifyNoInteractions(sourceDetailRepository)
|
||||
Mockito.verifyNoInteractions(calculateRepository)
|
||||
Mockito.verifyNoInteractions(memberRepository)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user