feat(agent-calculate): 에이전트별 정산 조회 기능을 추가한다

This commit is contained in:
2026-04-10 02:24:08 +09:00
parent 9e4cd1bb6e
commit bf67dab6a4
10 changed files with 2984 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
package kr.co.vividnext.sodalive.partner.agent.calculate
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.member.Member
import org.springframework.data.domain.Pageable
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
@RestController
@PreAuthorize("hasRole('AGENT')")
@RequestMapping("/agent/calculate")
class AgentCalculateController(private val service: AgentCalculateService) {
@GetMapping("/creator/list")
fun getAssignedCreators(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
val agent = member ?: throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getAssignedCreators(
agentId = agent.id!!,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
@GetMapping("/live-by-creator")
fun getCalculateLiveByCreator(
@RequestParam startDateStr: String,
@RequestParam endDateStr: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
val agent = member ?: throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getCalculateLiveByCreator(
startDateStr = startDateStr,
endDateStr = endDateStr,
agentId = agent.id!!,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
@GetMapping("/content-by-creator")
fun getCalculateContentByCreator(
@RequestParam startDateStr: String,
@RequestParam endDateStr: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
val agent = member ?: throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getCalculateContentByCreator(
startDateStr = startDateStr,
endDateStr = endDateStr,
agentId = agent.id!!,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
@GetMapping("/community-by-creator")
fun getCalculateCommunityByCreator(
@RequestParam startDateStr: String,
@RequestParam endDateStr: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
val agent = member ?: throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getCalculateCommunityByCreator(
startDateStr = startDateStr,
endDateStr = endDateStr,
agentId = agent.id!!,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
@GetMapping("/channel-donation-by-creator")
fun getChannelDonationByCreator(
@RequestParam startDateStr: String,
@RequestParam endDateStr: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
val agent = member ?: throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getChannelDonationByCreator(
startDateStr = startDateStr,
endDateStr = endDateStr,
agentId = agent.id!!,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
@GetMapping("/content-donation-by-creator")
fun getCalculateContentDonationByCreator(
@RequestParam startDateStr: String,
@RequestParam endDateStr: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
val agent = member ?: throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getCalculateContentDonationByCreator(
startDateStr = startDateStr,
endDateStr = endDateStr,
agentId = agent.id!!,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
}

View File

@@ -0,0 +1,666 @@
package kr.co.vividnext.sodalive.partner.agent.calculate
import com.querydsl.core.types.dsl.DateTimeExpression
import com.querydsl.core.types.dsl.Expressions
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.admin.calculate.ratio.QCreatorSettlementRatio.creatorSettlementRatio
import kr.co.vividnext.sodalive.can.use.CanUsage
import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
import kr.co.vividnext.sodalive.can.use.QUseCanCalculate.useCanCalculate
import kr.co.vividnext.sodalive.can.use.UseCanCalculateStatus
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
import kr.co.vividnext.sodalive.content.order.QOrder.order
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.QCreatorCommunity.creatorCommunity
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
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.ratio.QAgentSettlementRatio.agentSettlementRatio
import org.springframework.stereotype.Repository
import java.time.LocalDateTime
@Repository
class AgentCalculateQueryRepository(private val queryFactory: JPAQueryFactory) {
fun getAssignedCreatorTotalCount(agentId: Long, currentTime: LocalDateTime): Int {
return queryFactory
.select(agentCreatorRelation.id.count())
.from(agentCreatorRelation)
.where(
assignedToAgentAtCurrentTime(agentId = agentId, currentTime = currentTime)
)
.fetchOne()
?.toInt()
?: 0
}
fun getAssignedCreators(
agentId: Long,
offset: Long,
limit: Long,
currentTime: LocalDateTime
): List<GetAgentAssignedCreatorItem> {
return queryFactory
.select(
QGetAgentAssignedCreatorItem(
agentCreatorRelation.creator.id,
agentCreatorRelation.creator.nickname
)
)
.from(agentCreatorRelation)
.where(
assignedToAgentAtCurrentTime(agentId = agentId, currentTime = currentTime)
)
.orderBy(agentCreatorRelation.creator.id.desc())
.offset(offset)
.limit(limit)
.fetch()
}
fun getCalculateLiveByCreatorTotalCount(startDate: LocalDateTime, endDate: LocalDateTime, agentId: Long): Int {
return queryFactory
.select(member.id.countDistinct())
.from(useCan)
.innerJoin(useCan.room, liveRoom)
.innerJoin(liveRoom.member, member)
.innerJoin(agentCreatorRelation)
.on(
agentCreatorRelation.creator.id.eq(member.id)
.and(assignedToAgentAtEventTime(agentId, useCan.createdAt))
)
.where(
useCan.isRefund.isFalse
.and(useCan.createdAt.goe(startDate))
.and(useCan.createdAt.loe(endDate))
)
.fetchOne()
?.toInt()
?: 0
}
fun getCalculateLiveByCreator(
startDate: LocalDateTime,
endDate: LocalDateTime,
agentId: Long
): List<GetAgentCreatorSettlementSummaryQueryData> {
return getCalculateLiveByCreatorRows(startDate, endDate, agentId, creatorIds = null)
}
fun getCalculateLiveByCreator(
startDate: LocalDateTime,
endDate: LocalDateTime,
agentId: Long,
offset: Long,
limit: Long
): List<GetAgentCreatorSettlementSummaryQueryData> {
val creatorIds = queryFactory
.select(member.id)
.from(useCan)
.innerJoin(useCan.room, liveRoom)
.innerJoin(liveRoom.member, member)
.innerJoin(agentCreatorRelation)
.on(
agentCreatorRelation.creator.id.eq(member.id)
.and(assignedToAgentAtEventTime(agentId, useCan.createdAt))
)
.where(
useCan.isRefund.isFalse
.and(useCan.createdAt.goe(startDate))
.and(useCan.createdAt.loe(endDate))
)
.groupBy(member.id)
.orderBy(member.id.desc())
.offset(offset)
.limit(limit)
.fetch()
if (creatorIds.isEmpty()) {
return emptyList()
}
return getCalculateLiveByCreatorRows(startDate, endDate, agentId, creatorIds)
}
fun getCalculateContentByCreatorTotalCount(startDate: LocalDateTime, endDate: LocalDateTime, agentId: Long): Int {
return queryFactory
.select(member.id.countDistinct())
.from(order)
.innerJoin(order.audioContent, audioContent)
.innerJoin(audioContent.member, member)
.innerJoin(agentCreatorRelation)
.on(
agentCreatorRelation.creator.id.eq(member.id)
.and(assignedToAgentAtEventTime(agentId, order.createdAt))
)
.where(
order.createdAt.goe(startDate)
.and(order.createdAt.loe(endDate))
.and(order.isActive.isTrue)
)
.fetchOne()
?.toInt()
?: 0
}
fun getCalculateContentByCreator(
startDate: LocalDateTime,
endDate: LocalDateTime,
agentId: Long
): List<GetAgentCreatorSettlementSummaryQueryData> {
return getCalculateContentByCreatorRows(startDate, endDate, agentId, creatorIds = null)
}
fun getCalculateContentByCreator(
startDate: LocalDateTime,
endDate: LocalDateTime,
agentId: Long,
offset: Long,
limit: Long
): List<GetAgentCreatorSettlementSummaryQueryData> {
val creatorIds = queryFactory
.select(member.id)
.from(order)
.innerJoin(order.audioContent, audioContent)
.innerJoin(audioContent.member, member)
.innerJoin(agentCreatorRelation)
.on(
agentCreatorRelation.creator.id.eq(member.id)
.and(assignedToAgentAtEventTime(agentId, order.createdAt))
)
.where(
order.createdAt.goe(startDate)
.and(order.createdAt.loe(endDate))
.and(order.isActive.isTrue)
)
.groupBy(member.id)
.orderBy(member.id.desc())
.offset(offset)
.limit(limit)
.fetch()
if (creatorIds.isEmpty()) {
return emptyList()
}
return getCalculateContentByCreatorRows(startDate, endDate, agentId, creatorIds)
}
fun getCalculateCommunityByCreatorTotalCount(startDate: LocalDateTime, endDate: LocalDateTime, agentId: Long): Int {
return queryFactory
.select(member.id.countDistinct())
.from(useCan)
.innerJoin(useCan.communityPost, creatorCommunity)
.innerJoin(creatorCommunity.member, member)
.innerJoin(agentCreatorRelation)
.on(
agentCreatorRelation.creator.id.eq(member.id)
.and(assignedToAgentAtEventTime(agentId, useCan.createdAt))
)
.where(
useCan.isRefund.isFalse
.and(useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST))
.and(useCan.createdAt.goe(startDate))
.and(useCan.createdAt.loe(endDate))
)
.fetchOne()
?.toInt()
?: 0
}
fun getCalculateCommunityByCreator(
startDate: LocalDateTime,
endDate: LocalDateTime,
agentId: Long
): List<GetAgentCreatorSettlementSummaryQueryData> {
return getCalculateCommunityByCreatorRows(startDate, endDate, agentId, creatorIds = null)
}
fun getCalculateCommunityByCreator(
startDate: LocalDateTime,
endDate: LocalDateTime,
agentId: Long,
offset: Long,
limit: Long
): List<GetAgentCreatorSettlementSummaryQueryData> {
val creatorIds = queryFactory
.select(member.id)
.from(useCan)
.innerJoin(useCan.communityPost, creatorCommunity)
.innerJoin(creatorCommunity.member, member)
.innerJoin(agentCreatorRelation)
.on(
agentCreatorRelation.creator.id.eq(member.id)
.and(assignedToAgentAtEventTime(agentId, useCan.createdAt))
)
.where(
useCan.isRefund.isFalse
.and(useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST))
.and(useCan.createdAt.goe(startDate))
.and(useCan.createdAt.loe(endDate))
)
.groupBy(member.id)
.orderBy(member.id.desc())
.offset(offset)
.limit(limit)
.fetch()
if (creatorIds.isEmpty()) {
return emptyList()
}
return getCalculateCommunityByCreatorRows(startDate, endDate, agentId, creatorIds)
}
fun getCalculateContentDonationByCreatorTotalCount(
startDate: LocalDateTime,
endDate: LocalDateTime,
agentId: Long
): Int {
return queryFactory
.select(member.id.countDistinct())
.from(useCan)
.innerJoin(useCan.audioContent, audioContent)
.innerJoin(audioContent.member, member)
.innerJoin(agentCreatorRelation)
.on(
agentCreatorRelation.creator.id.eq(member.id)
.and(assignedToAgentAtEventTime(agentId, useCan.createdAt))
)
.where(
useCan.isRefund.isFalse
.and(useCan.canUsage.eq(CanUsage.DONATION))
.and(useCan.createdAt.goe(startDate))
.and(useCan.createdAt.loe(endDate))
)
.fetchOne()
?.toInt()
?: 0
}
fun getCalculateContentDonationByCreator(
startDate: LocalDateTime,
endDate: LocalDateTime,
agentId: Long
): List<GetAgentCreatorSettlementSummaryQueryData> {
return getCalculateContentDonationByCreatorRows(startDate, endDate, agentId, creatorIds = null)
}
fun getCalculateContentDonationByCreator(
startDate: LocalDateTime,
endDate: LocalDateTime,
agentId: Long,
offset: Long,
limit: Long
): List<GetAgentCreatorSettlementSummaryQueryData> {
val creatorIds = queryFactory
.select(member.id)
.from(useCan)
.innerJoin(useCan.audioContent, audioContent)
.innerJoin(audioContent.member, member)
.innerJoin(agentCreatorRelation)
.on(
agentCreatorRelation.creator.id.eq(member.id)
.and(assignedToAgentAtEventTime(agentId, useCan.createdAt))
)
.where(
useCan.isRefund.isFalse
.and(useCan.canUsage.eq(CanUsage.DONATION))
.and(useCan.createdAt.goe(startDate))
.and(useCan.createdAt.loe(endDate))
)
.groupBy(member.id)
.orderBy(member.id.desc())
.offset(offset)
.limit(limit)
.fetch()
if (creatorIds.isEmpty()) {
return emptyList()
}
return getCalculateContentDonationByCreatorRows(startDate, endDate, agentId, creatorIds)
}
fun getChannelDonationByCreatorTotalCount(startDate: LocalDateTime, endDate: LocalDateTime, agentId: Long): Int {
return queryFactory
.select(member.id.countDistinct())
.from(useCanCalculate)
.innerJoin(useCanCalculate.useCan, useCan)
.innerJoin(member)
.on(member.id.eq(useCanCalculate.recipientCreatorId))
.innerJoin(agentCreatorRelation)
.on(
agentCreatorRelation.creator.id.eq(member.id)
.and(assignedToAgentAtEventTime(agentId, useCan.createdAt))
)
.where(
useCan.canUsage.eq(CanUsage.CHANNEL_DONATION)
.and(useCan.isRefund.isFalse)
.and(useCanCalculate.status.eq(UseCanCalculateStatus.RECEIVED))
.and(useCan.createdAt.goe(startDate))
.and(useCan.createdAt.loe(endDate))
)
.fetchOne()
?.toInt()
?: 0
}
fun getChannelDonationByCreator(
startDate: LocalDateTime,
endDate: LocalDateTime,
agentId: Long
): List<GetAgentChannelDonationSettlementByCreatorQueryData> {
return getChannelDonationByCreatorRows(startDate, endDate, agentId, creatorIds = null)
}
fun getChannelDonationByCreator(
startDate: LocalDateTime,
endDate: LocalDateTime,
agentId: Long,
offset: Long,
limit: Long
): List<GetAgentChannelDonationSettlementByCreatorQueryData> {
val creatorIds = queryFactory
.select(member.id)
.from(useCanCalculate)
.innerJoin(useCanCalculate.useCan, useCan)
.innerJoin(member)
.on(member.id.eq(useCanCalculate.recipientCreatorId))
.innerJoin(agentCreatorRelation)
.on(
agentCreatorRelation.creator.id.eq(member.id)
.and(assignedToAgentAtEventTime(agentId, useCan.createdAt))
)
.where(
useCan.canUsage.eq(CanUsage.CHANNEL_DONATION)
.and(useCan.isRefund.isFalse)
.and(useCanCalculate.status.eq(UseCanCalculateStatus.RECEIVED))
.and(useCan.createdAt.goe(startDate))
.and(useCan.createdAt.loe(endDate))
)
.groupBy(member.id)
.orderBy(member.id.desc())
.offset(offset)
.limit(limit)
.fetch()
if (creatorIds.isEmpty()) {
return emptyList()
}
return getChannelDonationByCreatorRows(startDate, endDate, agentId, creatorIds)
}
private fun getCalculateLiveByCreatorRows(
startDate: LocalDateTime,
endDate: LocalDateTime,
agentId: Long,
creatorIds: List<Long>?
): List<GetAgentCreatorSettlementSummaryQueryData> {
return queryFactory
.select(
QGetAgentCreatorSettlementSummaryQueryData(
member.id,
member.nickname,
agentCreatorRelation.id,
agentSettlementRatio.id,
useCan.id.count(),
useCan.can.add(useCan.rewardCan).sum(),
creatorSettlementRatio.liveSettlementRatio,
agentSettlementRatio.settlementRatio
)
)
.from(useCan)
.innerJoin(useCan.room, liveRoom)
.innerJoin(liveRoom.member, member)
.innerJoin(agentCreatorRelation)
.on(
agentCreatorRelation.creator.id.eq(member.id)
.and(assignedToAgentAtEventTime(agentId, useCan.createdAt))
)
.leftJoin(creatorSettlementRatio)
.on(
member.id.eq(creatorSettlementRatio.member.id)
.and(creatorSettlementRatio.deletedAt.isNull)
)
.leftJoin(agentSettlementRatio)
.on(appliedAgentSettlementRatioAtEventTime(agentId, useCan.createdAt))
.where(
useCan.isRefund.isFalse
.and(useCan.createdAt.goe(startDate))
.and(useCan.createdAt.loe(endDate))
.and(if (!creatorIds.isNullOrEmpty()) member.id.`in`(creatorIds) else null)
)
.groupBy(
member.id,
member.nickname,
agentCreatorRelation.id,
agentSettlementRatio.id,
creatorSettlementRatio.liveSettlementRatio,
agentSettlementRatio.settlementRatio
)
.orderBy(member.id.desc())
.fetch()
}
private fun getCalculateContentByCreatorRows(
startDate: LocalDateTime,
endDate: LocalDateTime,
agentId: Long,
creatorIds: List<Long>?
): List<GetAgentCreatorSettlementSummaryQueryData> {
val contentSettlementRatio = audioContent.settlementRatio.coalesce(creatorSettlementRatio.contentSettlementRatio)
return queryFactory
.select(
QGetAgentCreatorSettlementSummaryQueryData(
member.id,
member.nickname,
agentCreatorRelation.id,
agentSettlementRatio.id,
order.id.count(),
order.can.sum(),
contentSettlementRatio,
agentSettlementRatio.settlementRatio
)
)
.from(order)
.innerJoin(order.audioContent, audioContent)
.innerJoin(audioContent.member, member)
.innerJoin(agentCreatorRelation)
.on(
agentCreatorRelation.creator.id.eq(member.id)
.and(assignedToAgentAtEventTime(agentId, order.createdAt))
)
.leftJoin(creatorSettlementRatio)
.on(
member.id.eq(creatorSettlementRatio.member.id)
.and(creatorSettlementRatio.deletedAt.isNull)
)
.leftJoin(agentSettlementRatio)
.on(appliedAgentSettlementRatioAtEventTime(agentId, order.createdAt))
.where(
order.createdAt.goe(startDate)
.and(order.createdAt.loe(endDate))
.and(order.isActive.isTrue)
.and(if (creatorIds != null && creatorIds.isNotEmpty()) member.id.`in`(creatorIds) else null)
)
.groupBy(
member.id,
member.nickname,
agentCreatorRelation.id,
agentSettlementRatio.id,
contentSettlementRatio,
agentSettlementRatio.settlementRatio
)
.orderBy(member.id.desc())
.fetch()
}
private fun getCalculateCommunityByCreatorRows(
startDate: LocalDateTime,
endDate: LocalDateTime,
agentId: Long,
creatorIds: List<Long>?
): List<GetAgentCreatorSettlementSummaryQueryData> {
return queryFactory
.select(
QGetAgentCreatorSettlementSummaryQueryData(
member.id,
member.nickname,
agentCreatorRelation.id,
agentSettlementRatio.id,
useCan.id.count(),
useCan.can.add(useCan.rewardCan).sum(),
creatorSettlementRatio.communitySettlementRatio,
agentSettlementRatio.settlementRatio
)
)
.from(useCan)
.innerJoin(useCan.communityPost, creatorCommunity)
.innerJoin(creatorCommunity.member, member)
.innerJoin(agentCreatorRelation)
.on(
agentCreatorRelation.creator.id.eq(member.id)
.and(assignedToAgentAtEventTime(agentId, useCan.createdAt))
)
.leftJoin(creatorSettlementRatio)
.on(
member.id.eq(creatorSettlementRatio.member.id)
.and(creatorSettlementRatio.deletedAt.isNull)
)
.leftJoin(agentSettlementRatio)
.on(appliedAgentSettlementRatioAtEventTime(agentId, useCan.createdAt))
.where(
useCan.isRefund.isFalse
.and(useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST))
.and(useCan.createdAt.goe(startDate))
.and(useCan.createdAt.loe(endDate))
.and(if (creatorIds != null && creatorIds.isNotEmpty()) member.id.`in`(creatorIds) else null)
)
.groupBy(
member.id,
member.nickname,
agentCreatorRelation.id,
agentSettlementRatio.id,
creatorSettlementRatio.communitySettlementRatio,
agentSettlementRatio.settlementRatio
)
.orderBy(member.id.desc())
.fetch()
}
private fun getCalculateContentDonationByCreatorRows(
startDate: LocalDateTime,
endDate: LocalDateTime,
agentId: Long,
creatorIds: List<Long>?
): List<GetAgentCreatorSettlementSummaryQueryData> {
return queryFactory
.select(
QGetAgentCreatorSettlementSummaryQueryData(
member.id,
member.nickname,
agentCreatorRelation.id,
agentSettlementRatio.id,
useCan.id.count(),
useCan.can.add(useCan.rewardCan).sum(),
Expressions.numberTemplate(Int::class.java, "70"),
agentSettlementRatio.settlementRatio
)
)
.from(useCan)
.innerJoin(useCan.audioContent, audioContent)
.innerJoin(audioContent.member, member)
.innerJoin(agentCreatorRelation)
.on(
agentCreatorRelation.creator.id.eq(member.id)
.and(assignedToAgentAtEventTime(agentId, useCan.createdAt))
)
.leftJoin(agentSettlementRatio)
.on(appliedAgentSettlementRatioAtEventTime(agentId, useCan.createdAt))
.where(
useCan.isRefund.isFalse
.and(useCan.canUsage.eq(CanUsage.DONATION))
.and(useCan.createdAt.goe(startDate))
.and(useCan.createdAt.loe(endDate))
.and(if (creatorIds != null && creatorIds.isNotEmpty()) member.id.`in`(creatorIds) else null)
)
.groupBy(
member.id,
member.nickname,
agentCreatorRelation.id,
agentSettlementRatio.id,
agentSettlementRatio.settlementRatio
)
.orderBy(member.id.desc())
.fetch()
}
private fun getChannelDonationByCreatorRows(
startDate: LocalDateTime,
endDate: LocalDateTime,
agentId: Long,
creatorIds: List<Long>?
): List<GetAgentChannelDonationSettlementByCreatorQueryData> {
return queryFactory
.select(
QGetAgentChannelDonationSettlementByCreatorQueryData(
member.id,
member.nickname,
agentCreatorRelation.id,
agentSettlementRatio.id,
useCan.id.countDistinct(),
useCanCalculate.can.sum(),
agentSettlementRatio.settlementRatio
)
)
.from(useCanCalculate)
.innerJoin(useCanCalculate.useCan, useCan)
.innerJoin(member)
.on(member.id.eq(useCanCalculate.recipientCreatorId))
.innerJoin(agentCreatorRelation)
.on(
agentCreatorRelation.creator.id.eq(member.id)
.and(assignedToAgentAtEventTime(agentId, useCan.createdAt))
)
.leftJoin(agentSettlementRatio)
.on(appliedAgentSettlementRatioAtEventTime(agentId, useCan.createdAt))
.where(
useCan.canUsage.eq(CanUsage.CHANNEL_DONATION)
.and(useCan.isRefund.isFalse)
.and(useCanCalculate.status.eq(UseCanCalculateStatus.RECEIVED))
.and(useCan.createdAt.goe(startDate))
.and(useCan.createdAt.loe(endDate))
.and(if (creatorIds != null && creatorIds.isNotEmpty()) member.id.`in`(creatorIds) else null)
)
.groupBy(
member.id,
member.nickname,
agentCreatorRelation.id,
agentSettlementRatio.id,
agentSettlementRatio.settlementRatio
)
.orderBy(member.id.desc())
.fetch()
}
private fun assignedToAgentAtEventTime(
agentId: Long,
eventTime: DateTimeExpression<LocalDateTime>
) = agentCreatorRelation.agent.id.eq(agentId)
.and(agentCreatorRelation.assignedAt.loe(eventTime))
.and(agentCreatorRelation.unassignedAt.isNull.or(agentCreatorRelation.unassignedAt.gt(eventTime)))
private fun assignedToAgentAtCurrentTime(
agentId: Long,
currentTime: LocalDateTime
) = agentCreatorRelation.agent.id.eq(agentId)
.and(agentCreatorRelation.assignedAt.loe(currentTime))
.and(agentCreatorRelation.unassignedAt.isNull.or(agentCreatorRelation.unassignedAt.gt(currentTime)))
private fun appliedAgentSettlementRatioAtEventTime(
agentId: Long,
eventTime: DateTimeExpression<LocalDateTime>
) = agentSettlementRatio.member.id.eq(agentId)
.and(agentSettlementRatio.effectiveFrom.loe(eventTime))
.and(agentSettlementRatio.effectiveTo.isNull.or(agentSettlementRatio.effectiveTo.gt(eventTime)))
}

View File

@@ -0,0 +1,219 @@
package kr.co.vividnext.sodalive.partner.agent.calculate
import kr.co.vividnext.sodalive.extensions.convertLocalDateTime
import kr.co.vividnext.sodalive.partner.agent.settlement.snapshot.AgentSettlementSnapshotRepository
import kr.co.vividnext.sodalive.partner.agent.settlement.snapshot.AgentSettlementSnapshotType
import kr.co.vividnext.sodalive.partner.agent.settlement.snapshot.toChannelDonationSettlementByCreatorItems
import kr.co.vividnext.sodalive.partner.agent.settlement.snapshot.toSettlementByCreatorItems
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime
@Service
class AgentCalculateService(
private val repository: AgentCalculateQueryRepository,
private val snapshotRepository: AgentSettlementSnapshotRepository
) {
@Transactional(readOnly = true)
fun getAssignedCreators(agentId: Long, offset: Long, limit: Long): GetAgentAssignedCreatorResponse {
val currentTime = LocalDateTime.now()
val totalCount = repository.getAssignedCreatorTotalCount(agentId = agentId, currentTime = currentTime)
val items = repository.getAssignedCreators(agentId = agentId, offset = offset, limit = limit, currentTime = currentTime)
return GetAgentAssignedCreatorResponse(totalCount = totalCount, items = items)
}
@Transactional(readOnly = true)
fun getCalculateLiveByCreator(
startDateStr: String,
endDateStr: String,
agentId: Long,
offset: Long,
limit: Long
): GetAgentSettlementByCreatorResponse {
return buildSettlementByCreatorResponse(
startDateStr = startDateStr,
endDateStr = endDateStr,
settlementType = AgentSettlementSnapshotType.LIVE,
agentId = agentId,
offset = offset,
limit = limit,
totalCountLoader = { startDate, endDate ->
repository.getCalculateLiveByCreatorTotalCount(startDate, endDate, agentId)
},
totalRowsLoader = { startDate, endDate ->
repository.getCalculateLiveByCreator(startDate, endDate, agentId)
},
pagedRowsLoader = { startDate, endDate ->
repository.getCalculateLiveByCreator(startDate, endDate, agentId, offset, limit)
}
)
}
@Transactional(readOnly = true)
fun getCalculateContentByCreator(
startDateStr: String,
endDateStr: String,
agentId: Long,
offset: Long,
limit: Long
): GetAgentSettlementByCreatorResponse {
return buildSettlementByCreatorResponse(
startDateStr = startDateStr,
endDateStr = endDateStr,
settlementType = AgentSettlementSnapshotType.CONTENT,
agentId = agentId,
offset = offset,
limit = limit,
totalCountLoader = { startDate, endDate ->
repository.getCalculateContentByCreatorTotalCount(startDate, endDate, agentId)
},
totalRowsLoader = { startDate, endDate ->
repository.getCalculateContentByCreator(startDate, endDate, agentId)
},
pagedRowsLoader = { startDate, endDate ->
repository.getCalculateContentByCreator(startDate, endDate, agentId, offset, limit)
}
)
}
@Transactional(readOnly = true)
fun getCalculateCommunityByCreator(
startDateStr: String,
endDateStr: String,
agentId: Long,
offset: Long,
limit: Long
): GetAgentSettlementByCreatorResponse {
return buildSettlementByCreatorResponse(
startDateStr = startDateStr,
endDateStr = endDateStr,
settlementType = AgentSettlementSnapshotType.COMMUNITY,
agentId = agentId,
offset = offset,
limit = limit,
totalCountLoader = { startDate, endDate ->
repository.getCalculateCommunityByCreatorTotalCount(startDate, endDate, agentId)
},
totalRowsLoader = { startDate, endDate ->
repository.getCalculateCommunityByCreator(startDate, endDate, agentId)
},
pagedRowsLoader = { startDate, endDate ->
repository.getCalculateCommunityByCreator(startDate, endDate, agentId, offset, limit)
}
)
}
@Transactional(readOnly = true)
fun getCalculateContentDonationByCreator(
startDateStr: String,
endDateStr: String,
agentId: Long,
offset: Long,
limit: Long
): GetAgentSettlementByCreatorResponse {
return buildSettlementByCreatorResponse(
startDateStr = startDateStr,
endDateStr = endDateStr,
settlementType = AgentSettlementSnapshotType.CONTENT_DONATION,
agentId = agentId,
offset = offset,
limit = limit,
totalCountLoader = { startDate, endDate ->
repository.getCalculateContentDonationByCreatorTotalCount(startDate, endDate, agentId)
},
totalRowsLoader = { startDate, endDate ->
repository.getCalculateContentDonationByCreator(startDate, endDate, agentId)
},
pagedRowsLoader = { startDate, endDate ->
repository.getCalculateContentDonationByCreator(startDate, endDate, agentId, offset, limit)
}
)
}
@Transactional(readOnly = true)
fun getChannelDonationByCreator(
startDateStr: String,
endDateStr: String,
agentId: Long,
offset: Long,
limit: Long
): GetAgentChannelDonationSettlementByCreatorResponse {
val (startDate, endDate) = toDateRange(startDateStr, endDateStr)
val snapshots = snapshotRepository.findAllByPeriodStartAndPeriodEndAndSettlementTypeAndAgentIdOrderByCreatorIdDesc(
periodStart = startDate,
periodEnd = endDate,
settlementType = AgentSettlementSnapshotType.CHANNEL_DONATION,
agentId = agentId
)
if (snapshots.isNotEmpty()) {
val total = snapshots.toChannelDonationSettlementByCreatorItems().toResponseTotal()
val items = snapshots.drop(offset.toInt()).take(limit.toInt()).toChannelDonationSettlementByCreatorItems()
return GetAgentChannelDonationSettlementByCreatorResponse(
totalCount = snapshots.size,
total = total,
items = items
)
}
val totalCount = repository.getChannelDonationByCreatorTotalCount(startDate, endDate, agentId)
val total = repository.getChannelDonationByCreator(startDate, endDate, agentId)
.toMergedResponseItems()
.toResponseTotal()
val items = repository.getChannelDonationByCreator(startDate, endDate, agentId, offset, limit)
.toMergedResponseItems()
return GetAgentChannelDonationSettlementByCreatorResponse(
totalCount = totalCount,
total = total,
items = items
)
}
private fun buildSettlementByCreatorResponse(
startDateStr: String,
endDateStr: String,
settlementType: AgentSettlementSnapshotType,
agentId: Long,
offset: Long,
limit: Long,
totalCountLoader: (LocalDateTime, LocalDateTime) -> Int,
totalRowsLoader: (LocalDateTime, LocalDateTime) -> List<GetAgentCreatorSettlementSummaryQueryData>,
pagedRowsLoader: (LocalDateTime, LocalDateTime) -> List<GetAgentCreatorSettlementSummaryQueryData>
): GetAgentSettlementByCreatorResponse {
val (startDate, endDate) = toDateRange(startDateStr, endDateStr)
val snapshots = snapshotRepository.findAllByPeriodStartAndPeriodEndAndSettlementTypeAndAgentIdOrderByCreatorIdDesc(
periodStart = startDate,
periodEnd = endDate,
settlementType = settlementType,
agentId = agentId
)
if (snapshots.isNotEmpty()) {
val total = snapshots.toSettlementByCreatorItems().toResponseTotal()
val items = snapshots.drop(offset.toInt()).take(limit.toInt()).toSettlementByCreatorItems()
return GetAgentSettlementByCreatorResponse(
totalCount = snapshots.size,
total = total,
items = items
)
}
val totalCount = totalCountLoader(startDate, endDate)
val total = totalRowsLoader(startDate, endDate)
.toMergedResponseItems()
.toResponseTotal()
val items = pagedRowsLoader(startDate, endDate)
.toMergedResponseItems()
return GetAgentSettlementByCreatorResponse(
totalCount = totalCount,
total = total,
items = items
)
}
private fun toDateRange(startDateStr: String, endDateStr: String): Pair<LocalDateTime, LocalDateTime> {
val startDate = startDateStr.convertLocalDateTime()
val endDate = endDateStr.convertLocalDateTime(hour = 23, minute = 59, second = 59)
return startDate to endDate
}
}

View File

@@ -0,0 +1,13 @@
package kr.co.vividnext.sodalive.partner.agent.calculate
import com.querydsl.core.annotations.QueryProjection
data class GetAgentAssignedCreatorResponse(
val totalCount: Int,
val items: List<GetAgentAssignedCreatorItem>
)
data class GetAgentAssignedCreatorItem @QueryProjection constructor(
val creatorId: Long,
val creatorNickname: String
)

View File

@@ -0,0 +1,102 @@
package kr.co.vividnext.sodalive.partner.agent.calculate
import com.querydsl.core.annotations.QueryProjection
import kr.co.vividnext.sodalive.calculate.channelDonation.ChannelDonationSettlementCalculator
import java.math.BigDecimal
import java.math.RoundingMode
data class GetAgentChannelDonationSettlementByCreatorResponse(
val totalCount: Int,
val total: GetAgentChannelDonationSettlementTotal,
val items: List<GetAgentChannelDonationSettlementByCreatorItem>
)
data class GetAgentChannelDonationSettlementTotal(
val count: Int,
val totalCan: Int,
val krw: Int,
val fee: Int,
val settlementAmount: Int,
val withholdingTax: Int,
val depositAmount: Int,
val agentSettlementAmount: Int
)
data class GetAgentChannelDonationSettlementByCreatorItem(
val creatorId: Long,
val creatorNickname: String,
val count: Int,
val totalCan: Int,
val krw: Int,
val fee: Int,
val settlementAmount: Int,
val withholdingTax: Int,
val depositAmount: Int,
val agentSettlementAmount: Int
)
data class GetAgentChannelDonationSettlementByCreatorQueryData @QueryProjection constructor(
val creatorId: Long,
val creatorNickname: String,
val assignmentId: Long? = null,
val agentSettlementRatioId: Long? = null,
val count: Long,
val totalCan: Int,
val agentSettlementRatio: Int? = null
) {
fun toResponseItem(): GetAgentChannelDonationSettlementByCreatorItem {
val amount = ChannelDonationSettlementCalculator.calculate(totalCan)
return GetAgentChannelDonationSettlementByCreatorItem(
creatorId = creatorId,
creatorNickname = creatorNickname,
count = count.toInt(),
totalCan = totalCan,
krw = amount.krw,
fee = amount.fee,
settlementAmount = amount.settlementAmount,
withholdingTax = amount.withholdingTax,
depositAmount = amount.depositAmount,
agentSettlementAmount = BigDecimal(amount.settlementAmount)
.multiply(BigDecimal(agentSettlementRatio ?: DEFAULT_AGENT_SETTLEMENT_RATIO).divide(BigDecimal("100")))
.setScale(0, RoundingMode.HALF_UP)
.toInt()
)
}
companion object {
private const val DEFAULT_AGENT_SETTLEMENT_RATIO = 10
}
}
fun List<GetAgentChannelDonationSettlementByCreatorQueryData>.toMergedResponseItems():
List<GetAgentChannelDonationSettlementByCreatorItem> {
return map { it.toResponseItem() }
.groupBy { it.creatorId }
.map { (_, items) ->
items.reduce { acc, item ->
acc.copy(
count = acc.count + item.count,
totalCan = acc.totalCan + item.totalCan,
krw = acc.krw + item.krw,
fee = acc.fee + item.fee,
settlementAmount = acc.settlementAmount + item.settlementAmount,
withholdingTax = acc.withholdingTax + item.withholdingTax,
depositAmount = acc.depositAmount + item.depositAmount,
agentSettlementAmount = acc.agentSettlementAmount + item.agentSettlementAmount
)
}
}
}
fun List<GetAgentChannelDonationSettlementByCreatorItem>.toResponseTotal(): GetAgentChannelDonationSettlementTotal {
return GetAgentChannelDonationSettlementTotal(
count = sumOf { it.count },
totalCan = sumOf { it.totalCan },
krw = sumOf { it.krw },
fee = sumOf { it.fee },
settlementAmount = sumOf { it.settlementAmount },
withholdingTax = sumOf { it.withholdingTax },
depositAmount = sumOf { it.depositAmount },
agentSettlementAmount = sumOf { it.agentSettlementAmount }
)
}

View File

@@ -0,0 +1,77 @@
package kr.co.vividnext.sodalive.partner.agent.calculate
import com.querydsl.core.annotations.QueryProjection
import java.math.BigDecimal
import java.math.RoundingMode
data class GetAgentCreatorSettlementSummaryQueryData @QueryProjection constructor(
val creatorId: Long,
val creatorNickname: String,
val assignmentId: Long? = null,
val agentSettlementRatioId: Long? = null,
val count: Long,
val totalCan: Int,
val settlementRatio: Int?,
val agentSettlementRatio: Int? = null
) {
fun toResponseItem(): GetAgentSettlementByCreatorItem {
val totalKrw = BigDecimal(totalCan).multiply(KRW_PER_CAN)
val fee = totalKrw.multiply(PAYMENT_FEE_RATE)
val settlementAmount = totalKrw.subtract(fee)
.multiply(BigDecimal(settlementRatio ?: DEFAULT_SETTLEMENT_RATIO).divide(PERCENT_DIVISOR))
val tax = settlementAmount.multiply(TAX_RATE)
val depositAmount = settlementAmount.subtract(tax)
val roundedSettlementAmount = settlementAmount.setScale(0, RoundingMode.HALF_UP).toInt()
return GetAgentSettlementByCreatorItem(
creatorId = creatorId,
creatorNickname = creatorNickname,
count = count.toInt(),
totalCan = totalCan,
krw = totalKrw.toInt(),
fee = fee.setScale(0, RoundingMode.HALF_UP).toInt(),
settlementAmount = roundedSettlementAmount,
tax = tax.setScale(0, RoundingMode.HALF_UP).toInt(),
depositAmount = depositAmount.setScale(0, RoundingMode.HALF_UP).toInt(),
agentSettlementAmount = calculateAgentSettlementAmount(
settlementAmount = roundedSettlementAmount,
agentSettlementRatio = agentSettlementRatio ?: DEFAULT_AGENT_SETTLEMENT_RATIO
)
)
}
companion object {
private val KRW_PER_CAN = BigDecimal("100")
private val PAYMENT_FEE_RATE = BigDecimal("0.066")
private val TAX_RATE = BigDecimal("0.033")
private val PERCENT_DIVISOR = BigDecimal("100")
private const val DEFAULT_SETTLEMENT_RATIO = 70
private const val DEFAULT_AGENT_SETTLEMENT_RATIO = 10
private fun calculateAgentSettlementAmount(settlementAmount: Int, agentSettlementRatio: Int): Int {
return BigDecimal(settlementAmount)
.multiply(BigDecimal(agentSettlementRatio).divide(PERCENT_DIVISOR))
.setScale(0, RoundingMode.HALF_UP)
.toInt()
}
}
}
fun List<GetAgentCreatorSettlementSummaryQueryData>.toMergedResponseItems(): List<GetAgentSettlementByCreatorItem> {
return map { it.toResponseItem() }
.groupBy { it.creatorId }
.map { (_, items) ->
items.reduce { acc, item ->
acc.copy(
count = acc.count + item.count,
totalCan = acc.totalCan + item.totalCan,
krw = acc.krw + item.krw,
fee = acc.fee + item.fee,
settlementAmount = acc.settlementAmount + item.settlementAmount,
tax = acc.tax + item.tax,
depositAmount = acc.depositAmount + item.depositAmount,
agentSettlementAmount = acc.agentSettlementAmount + item.agentSettlementAmount
)
}
}
}

View File

@@ -0,0 +1,44 @@
package kr.co.vividnext.sodalive.partner.agent.calculate
data class GetAgentSettlementByCreatorResponse(
val totalCount: Int,
val total: GetAgentSettlementByCreatorTotal,
val items: List<GetAgentSettlementByCreatorItem>
)
data class GetAgentSettlementByCreatorTotal(
val count: Int,
val totalCan: Int,
val krw: Int,
val fee: Int,
val settlementAmount: Int,
val tax: Int,
val depositAmount: Int,
val agentSettlementAmount: Int
)
data class GetAgentSettlementByCreatorItem(
val creatorId: Long,
val creatorNickname: String,
val count: Int,
val totalCan: Int,
val krw: Int,
val fee: Int,
val settlementAmount: Int,
val tax: Int,
val depositAmount: Int,
val agentSettlementAmount: Int
)
fun List<GetAgentSettlementByCreatorItem>.toResponseTotal(): GetAgentSettlementByCreatorTotal {
return GetAgentSettlementByCreatorTotal(
count = sumOf { it.count },
totalCan = sumOf { it.totalCan },
krw = sumOf { it.krw },
fee = sumOf { it.fee },
settlementAmount = sumOf { it.settlementAmount },
tax = sumOf { it.tax },
depositAmount = sumOf { it.depositAmount },
agentSettlementAmount = sumOf { it.agentSettlementAmount }
)
}