feat(admin-chat-calculate): 캐릭터 정산 API에 채팅 횟수 구매(CHAT_QUOTA_PURCHASE) 추가
This commit is contained in:
		| @@ -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() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user