From 37516a00724aa6ea2990d9e0e31e141f4a4b86b4 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 5 Mar 2025 21:49:33 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A7=88=EC=BC=80=ED=8C=85=20-=20=EA=B4=91?= =?UTF-8?q?=EA=B3=A0=20=ED=86=B5=EA=B3=84=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistics/AdminAdStatisticsController.kt | 21 +++ .../statistics/AdminAdStatisticsRepository.kt | 129 ++++++++++++++++++ .../statistics/AdminAdStatisticsService.kt | 18 +++ .../GetAdminAdStatisticsResponse.kt | 22 +++ 4 files changed, 190 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/marketing/statistics/AdminAdStatisticsController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/marketing/statistics/AdminAdStatisticsRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/marketing/statistics/AdminAdStatisticsService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/marketing/statistics/GetAdminAdStatisticsResponse.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/marketing/statistics/AdminAdStatisticsController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/marketing/statistics/AdminAdStatisticsController.kt new file mode 100644 index 0000000..1848592 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/marketing/statistics/AdminAdStatisticsController.kt @@ -0,0 +1,21 @@ +package kr.co.vividnext.sodalive.admin.marketing.statistics + +import kr.co.vividnext.sodalive.common.ApiResponse +import org.springframework.data.domain.Pageable +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@PreAuthorize("hasRole('ADMIN')") +@RequestMapping("/admin/marketing/statistics") +class AdminAdStatisticsController(private val service: AdminAdStatisticsService) { + @GetMapping + fun getStatistics(pageable: Pageable) = ApiResponse.ok( + service.getStatistics( + offset = pageable.offset, + limit = pageable.pageSize.toLong() + ) + ) +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/marketing/statistics/AdminAdStatisticsRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/marketing/statistics/AdminAdStatisticsRepository.kt new file mode 100644 index 0000000..6cd8046 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/marketing/statistics/AdminAdStatisticsRepository.kt @@ -0,0 +1,129 @@ +package kr.co.vividnext.sodalive.admin.marketing.statistics + +import com.querydsl.core.types.dsl.CaseBuilder +import com.querydsl.core.types.dsl.DateTimePath +import com.querydsl.core.types.dsl.Expressions +import com.querydsl.core.types.dsl.NumberExpression +import com.querydsl.core.types.dsl.StringTemplate +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.marketing.AdTrackingHistoryType +import kr.co.vividnext.sodalive.marketing.QAdTrackingHistory.adTrackingHistory +import org.springframework.stereotype.Repository +import java.time.LocalDateTime + +@Repository +class AdminAdStatisticsRepository(private val queryFactory: JPAQueryFactory) { + fun getAdStatisticsDataTotalCount(): Int { + return queryFactory + .select(adTrackingHistory.id.pid) + .from(adTrackingHistory) + .groupBy( + getFormattedDate(adTrackingHistory.id.createdAt), + adTrackingHistory.mediaGroup, + adTrackingHistory.id.pid, + adTrackingHistory.pidName + ) + .fetch() + .size + } + + fun getAdStatisticsDataList(offset: Long, limit: Long): List { + val signUpCount = CaseBuilder() + .`when`(adTrackingHistory.id.type.eq(AdTrackingHistoryType.SIGNUP)) + .then(1) + .otherwise(0) + .sum() + + val firstPaymentCount = CaseBuilder() + .`when`(adTrackingHistory.id.type.eq(AdTrackingHistoryType.FIRST_PAYMENT)) + .then(1) + .otherwise(0) + .sum() + + val firstPaymentTotalAmount = CaseBuilder() + .`when`(adTrackingHistory.id.type.eq(AdTrackingHistoryType.FIRST_PAYMENT)) + .then(adTrackingHistory.price) + .otherwise(Expressions.constant(0.0)) + .sum() + + val repeatPaymentCount = CaseBuilder() + .`when`(adTrackingHistory.id.type.eq(AdTrackingHistoryType.REPEAT_PAYMENT)) + .then(1) + .otherwise(0) + .sum() + + val repeatPaymentTotalAmount = CaseBuilder() + .`when`(adTrackingHistory.id.type.eq(AdTrackingHistoryType.REPEAT_PAYMENT)) + .then(adTrackingHistory.price) + .otherwise(Expressions.constant(0.0)) + .sum() + + val allPaymentCount = CaseBuilder() + .`when`( + adTrackingHistory.id.type.eq(AdTrackingHistoryType.FIRST_PAYMENT) + .or(adTrackingHistory.id.type.eq(AdTrackingHistoryType.REPEAT_PAYMENT)) + ) + .then(1) + .otherwise(0) + .sum() + + val allPaymentTotalAmount = CaseBuilder() + .`when`( + adTrackingHistory.id.type.eq(AdTrackingHistoryType.FIRST_PAYMENT) + .or(adTrackingHistory.id.type.eq(AdTrackingHistoryType.REPEAT_PAYMENT)) + ) + .then(adTrackingHistory.price) + .otherwise(Expressions.constant(0.0)) + .sum() + + return queryFactory + .select( + QGetAdminAdStatisticsItem( + getFormattedDate(adTrackingHistory.id.createdAt), + adTrackingHistory.mediaGroup, + adTrackingHistory.id.pid, + adTrackingHistory.pidName, + signUpCount, + firstPaymentCount, + roundedValueDecimalPlaces2(firstPaymentTotalAmount), + repeatPaymentCount, + roundedValueDecimalPlaces2(repeatPaymentTotalAmount), + allPaymentCount, + roundedValueDecimalPlaces2(allPaymentTotalAmount) + ) + ) + .from(adTrackingHistory) + .groupBy( + getFormattedDate(adTrackingHistory.id.createdAt), + adTrackingHistory.mediaGroup, + adTrackingHistory.id.pid, + adTrackingHistory.pidName + ) + .offset(offset) + .limit(limit) + .fetch() + } + + private fun getFormattedDate(dateTimePath: DateTimePath): StringTemplate { + return Expressions.stringTemplate( + "DATE_FORMAT({0}, {1})", + Expressions.dateTimeTemplate( + LocalDateTime::class.java, + "CONVERT_TZ({0},{1},{2})", + dateTimePath, + "UTC", + "Asia/Seoul" + ), + "%Y-%m-%d" + ) + } + + private fun roundedValueDecimalPlaces2(valueExpression: NumberExpression): NumberExpression { + return Expressions.numberTemplate( + Double::class.java, + "ROUND({0}, {1})", + valueExpression, + 2 + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/marketing/statistics/AdminAdStatisticsService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/marketing/statistics/AdminAdStatisticsService.kt new file mode 100644 index 0000000..6e1bb5c --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/marketing/statistics/AdminAdStatisticsService.kt @@ -0,0 +1,18 @@ +package kr.co.vividnext.sodalive.admin.marketing.statistics + +import org.springframework.stereotype.Service + +@Service +class AdminAdStatisticsService( + private val repository: AdminAdStatisticsRepository +) { + fun getStatistics(offset: Long, limit: Long): GetAdminAdStatisticsResponse { + val totalCount = repository.getAdStatisticsDataTotalCount() + val items = repository.getAdStatisticsDataList(offset, limit) + + return GetAdminAdStatisticsResponse( + totalCount = totalCount, + items = items + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/marketing/statistics/GetAdminAdStatisticsResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/marketing/statistics/GetAdminAdStatisticsResponse.kt new file mode 100644 index 0000000..1a7adff --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/marketing/statistics/GetAdminAdStatisticsResponse.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.admin.marketing.statistics + +import com.querydsl.core.annotations.QueryProjection + +data class GetAdminAdStatisticsResponse( + val totalCount: Int, + val items: List +) + +data class GetAdminAdStatisticsItem @QueryProjection constructor( + val date: String, + val mediaGroup: String, + val pid: String, + val pidName: String, + val signUpCount: Int, + val firstPaymentCount: Int, + val firstPaymentTotalAmount: Double, + val repeatPaymentCount: Int, + val repeatPaymentTotalAmount: Double, + val allPaymentCount: Int, + val allPaymentTotalAmount: Double +)