feat(admin-chat-calculate): 캐릭터 정산 API에 채팅 횟수 구매(CHAT_QUOTA_PURCHASE) 추가
This commit is contained in:
parent
0ed29c6097
commit
634bf759ca
|
@ -1,10 +1,11 @@
|
||||||
package kr.co.vividnext.sodalive.admin.chat.calculate
|
package kr.co.vividnext.sodalive.admin.chat.calculate
|
||||||
|
|
||||||
|
import com.querydsl.core.types.Projections
|
||||||
import com.querydsl.core.types.dsl.CaseBuilder
|
import com.querydsl.core.types.dsl.CaseBuilder
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||||
import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
|
import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
|
||||||
import kr.co.vividnext.sodalive.chat.character.QChatCharacter.chatCharacter
|
import kr.co.vividnext.sodalive.chat.character.QChatCharacter
|
||||||
import kr.co.vividnext.sodalive.chat.character.image.QCharacterImage.characterImage
|
import kr.co.vividnext.sodalive.chat.character.image.QCharacterImage.characterImage
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
@ -34,37 +35,60 @@ class AdminChatCalculateQueryRepository(
|
||||||
.then(useCan.can.add(useCan.rewardCan))
|
.then(useCan.can.add(useCan.rewardCan))
|
||||||
.otherwise(0)
|
.otherwise(0)
|
||||||
|
|
||||||
|
val quotaCanExpr = CaseBuilder()
|
||||||
|
.`when`(useCan.canUsage.eq(CanUsage.CHAT_QUOTA_PURCHASE))
|
||||||
|
.then(useCan.can.add(useCan.rewardCan))
|
||||||
|
.otherwise(0)
|
||||||
|
|
||||||
val imageSum = imageCanExpr.sum()
|
val imageSum = imageCanExpr.sum()
|
||||||
val messageSum = messageCanExpr.sum()
|
val messageSum = messageCanExpr.sum()
|
||||||
val totalSum = imageSum.add(messageSum)
|
val quotaSum = quotaCanExpr.sum()
|
||||||
|
val totalSum = imageSum.add(messageSum).add(quotaSum)
|
||||||
|
|
||||||
|
// 캐릭터 조인: 이미지 경로를 통한 캐릭터(c1) + characterId 직접 지정(c2)
|
||||||
|
val c1 = QChatCharacter("c1")
|
||||||
|
val c2 = QChatCharacter("c2")
|
||||||
|
|
||||||
|
val characterIdExpr = c1.id.coalesce(c2.id)
|
||||||
|
val characterNameExpr = c1.name.coalesce(c2.name)
|
||||||
|
val characterImagePathExpr = c1.imagePath.coalesce(c2.imagePath)
|
||||||
|
|
||||||
val query = queryFactory
|
val query = queryFactory
|
||||||
.select(
|
.select(
|
||||||
QChatCharacterCalculateQueryData(
|
Projections.constructor(
|
||||||
chatCharacter.id,
|
ChatCharacterCalculateQueryData::class.java,
|
||||||
chatCharacter.name,
|
characterIdExpr,
|
||||||
chatCharacter.imagePath.prepend("/").prepend(imageHost),
|
characterNameExpr,
|
||||||
|
characterImagePathExpr.prepend("/").prepend(imageHost),
|
||||||
imageSum,
|
imageSum,
|
||||||
messageSum
|
messageSum,
|
||||||
|
quotaSum
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(useCan)
|
.from(useCan)
|
||||||
.innerJoin(useCan.characterImage, characterImage)
|
.leftJoin(useCan.characterImage, characterImage)
|
||||||
.innerJoin(characterImage.chatCharacter, chatCharacter)
|
.leftJoin(characterImage.chatCharacter, c1)
|
||||||
|
.leftJoin(c2).on(c2.id.eq(useCan.characterId))
|
||||||
.where(
|
.where(
|
||||||
useCan.isRefund.isFalse
|
useCan.isRefund.isFalse
|
||||||
.and(useCan.canUsage.`in`(CanUsage.CHARACTER_IMAGE_PURCHASE, CanUsage.CHAT_MESSAGE_PURCHASE))
|
.and(
|
||||||
|
useCan.canUsage.`in`(
|
||||||
|
CanUsage.CHARACTER_IMAGE_PURCHASE,
|
||||||
|
CanUsage.CHAT_MESSAGE_PURCHASE,
|
||||||
|
CanUsage.CHAT_QUOTA_PURCHASE
|
||||||
|
)
|
||||||
|
)
|
||||||
.and(useCan.createdAt.goe(startUtc))
|
.and(useCan.createdAt.goe(startUtc))
|
||||||
.and(useCan.createdAt.loe(endInclusiveUtc))
|
.and(useCan.createdAt.loe(endInclusiveUtc))
|
||||||
)
|
)
|
||||||
.groupBy(chatCharacter.id, chatCharacter.name, chatCharacter.imagePath)
|
.groupBy(characterIdExpr, characterNameExpr, characterImagePathExpr)
|
||||||
|
|
||||||
when (sort) {
|
when (sort) {
|
||||||
ChatCharacterCalculateSort.TOTAL_SALES_DESC ->
|
ChatCharacterCalculateSort.TOTAL_SALES_DESC ->
|
||||||
query.orderBy(totalSum.desc(), chatCharacter.id.desc())
|
query.orderBy(totalSum.desc(), characterIdExpr.desc())
|
||||||
|
|
||||||
ChatCharacterCalculateSort.LATEST_DESC ->
|
ChatCharacterCalculateSort.LATEST_DESC ->
|
||||||
query.orderBy(chatCharacter.id.desc(), totalSum.desc())
|
query.orderBy(characterIdExpr.desc(), totalSum.desc())
|
||||||
}
|
}
|
||||||
|
|
||||||
return query
|
return query
|
||||||
|
@ -77,18 +101,29 @@ class AdminChatCalculateQueryRepository(
|
||||||
startUtc: LocalDateTime,
|
startUtc: LocalDateTime,
|
||||||
endInclusiveUtc: LocalDateTime
|
endInclusiveUtc: LocalDateTime
|
||||||
): Int {
|
): Int {
|
||||||
|
val c1 = QChatCharacter("c1")
|
||||||
|
val c2 = QChatCharacter("c2")
|
||||||
|
val characterIdExpr = c1.id.coalesce(c2.id)
|
||||||
|
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(chatCharacter.id)
|
.select(characterIdExpr)
|
||||||
.from(useCan)
|
.from(useCan)
|
||||||
.innerJoin(useCan.characterImage, characterImage)
|
.leftJoin(useCan.characterImage, characterImage)
|
||||||
.innerJoin(characterImage.chatCharacter, chatCharacter)
|
.leftJoin(characterImage.chatCharacter, c1)
|
||||||
|
.leftJoin(c2).on(c2.id.eq(useCan.characterId))
|
||||||
.where(
|
.where(
|
||||||
useCan.isRefund.isFalse
|
useCan.isRefund.isFalse
|
||||||
.and(useCan.canUsage.`in`(CanUsage.CHARACTER_IMAGE_PURCHASE, CanUsage.CHAT_MESSAGE_PURCHASE))
|
.and(
|
||||||
|
useCan.canUsage.`in`(
|
||||||
|
CanUsage.CHARACTER_IMAGE_PURCHASE,
|
||||||
|
CanUsage.CHAT_MESSAGE_PURCHASE,
|
||||||
|
CanUsage.CHAT_QUOTA_PURCHASE
|
||||||
|
)
|
||||||
|
)
|
||||||
.and(useCan.createdAt.goe(startUtc))
|
.and(useCan.createdAt.goe(startUtc))
|
||||||
.and(useCan.createdAt.loe(endInclusiveUtc))
|
.and(useCan.createdAt.loe(endInclusiveUtc))
|
||||||
)
|
)
|
||||||
.groupBy(chatCharacter.id)
|
.groupBy(characterIdExpr)
|
||||||
.fetch()
|
.fetch()
|
||||||
.size
|
.size
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package kr.co.vividnext.sodalive.admin.chat.calculate
|
package kr.co.vividnext.sodalive.admin.chat.calculate
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.extensions.convertLocalDateTime
|
import kr.co.vividnext.sodalive.extensions.convertLocalDateTime
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
@ -27,11 +28,16 @@ class AdminChatCalculateService(
|
||||||
val endDate = LocalDate.parse(endDateStr, dateFormatter)
|
val endDate = LocalDate.parse(endDateStr, dateFormatter)
|
||||||
val todayKst = LocalDate.now(kstZone)
|
val todayKst = LocalDate.now(kstZone)
|
||||||
|
|
||||||
require(!endDate.isAfter(todayKst)) { "끝 날짜는 오늘 날짜까지만 입력 가능합니다." }
|
if (endDate.isAfter(todayKst)) {
|
||||||
require(!startDate.isAfter(endDate)) { "시작 날짜는 끝 날짜보다 이후일 수 없습니다." }
|
throw SodaException("끝 날짜는 오늘 날짜까지만 입력 가능합니다.")
|
||||||
require(!endDate.isAfter(startDate.plusMonths(6))) { "조회 가능 기간은 최대 6개월입니다." }
|
}
|
||||||
|
if (startDate.isAfter(endDate)) {
|
||||||
|
throw SodaException("시작 날짜는 끝 날짜보다 이후일 수 없습니다.")
|
||||||
|
}
|
||||||
|
if (endDate.isAfter(startDate.plusMonths(6))) {
|
||||||
|
throw SodaException("조회 가능 기간은 최대 6개월입니다.")
|
||||||
|
}
|
||||||
|
|
||||||
// 입력은 KST, UTC로 변환해 [start, end(미포함)] 조회
|
|
||||||
val startUtc = startDateStr.convertLocalDateTime()
|
val startUtc = startDateStr.convertLocalDateTime()
|
||||||
val endInclusiveUtc = endDateStr.convertLocalDateTime(hour = 23, minute = 59, second = 59)
|
val endInclusiveUtc = endDateStr.convertLocalDateTime(hour = 23, minute = 59, second = 59)
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,8 @@ data class ChatCharacterCalculateQueryData @QueryProjection constructor(
|
||||||
val characterName: String,
|
val characterName: String,
|
||||||
val characterImagePath: String?,
|
val characterImagePath: String?,
|
||||||
val imagePurchaseCan: Int?,
|
val imagePurchaseCan: Int?,
|
||||||
val messagePurchaseCan: Int?
|
val messagePurchaseCan: Int?,
|
||||||
|
val quotaPurchaseCan: Int?
|
||||||
)
|
)
|
||||||
|
|
||||||
// 응답 DTO (아이템)
|
// 응답 DTO (아이템)
|
||||||
|
@ -27,6 +28,7 @@ data class ChatCharacterCalculateItem(
|
||||||
@JsonProperty("name") val name: String,
|
@JsonProperty("name") val name: String,
|
||||||
@JsonProperty("imagePurchaseCan") val imagePurchaseCan: Int,
|
@JsonProperty("imagePurchaseCan") val imagePurchaseCan: Int,
|
||||||
@JsonProperty("messagePurchaseCan") val messagePurchaseCan: Int,
|
@JsonProperty("messagePurchaseCan") val messagePurchaseCan: Int,
|
||||||
|
@JsonProperty("quotaPurchaseCan") val quotaPurchaseCan: Int,
|
||||||
@JsonProperty("totalCan") val totalCan: Int,
|
@JsonProperty("totalCan") val totalCan: Int,
|
||||||
@JsonProperty("totalKrw") val totalKrw: Int,
|
@JsonProperty("totalKrw") val totalKrw: Int,
|
||||||
@JsonProperty("settlementKrw") val settlementKrw: Int
|
@JsonProperty("settlementKrw") val settlementKrw: Int
|
||||||
|
@ -41,7 +43,8 @@ data class ChatCharacterCalculateResponse(
|
||||||
fun ChatCharacterCalculateQueryData.toItem(): ChatCharacterCalculateItem {
|
fun ChatCharacterCalculateQueryData.toItem(): ChatCharacterCalculateItem {
|
||||||
val image = imagePurchaseCan ?: 0
|
val image = imagePurchaseCan ?: 0
|
||||||
val message = messagePurchaseCan ?: 0
|
val message = messagePurchaseCan ?: 0
|
||||||
val total = image + message
|
val quota = quotaPurchaseCan ?: 0
|
||||||
|
val total = image + message + quota
|
||||||
val totalKrw = BigDecimal(total).multiply(BigDecimal(100))
|
val totalKrw = BigDecimal(total).multiply(BigDecimal(100))
|
||||||
val settlement = totalKrw.multiply(BigDecimal("0.10")).setScale(0, RoundingMode.HALF_UP)
|
val settlement = totalKrw.multiply(BigDecimal("0.10")).setScale(0, RoundingMode.HALF_UP)
|
||||||
|
|
||||||
|
@ -51,6 +54,7 @@ fun ChatCharacterCalculateQueryData.toItem(): ChatCharacterCalculateItem {
|
||||||
name = characterName,
|
name = characterName,
|
||||||
imagePurchaseCan = image,
|
imagePurchaseCan = image,
|
||||||
messagePurchaseCan = message,
|
messagePurchaseCan = message,
|
||||||
|
quotaPurchaseCan = quota,
|
||||||
totalCan = total,
|
totalCan = total,
|
||||||
totalKrw = totalKrw.toInt(),
|
totalKrw = totalKrw.toInt(),
|
||||||
settlementKrw = settlement.toInt()
|
settlementKrw = settlement.toInt()
|
||||||
|
|
Loading…
Reference in New Issue