Compare commits
No commits in common. "a76a84123805bd392e6c53a5d475ab71e46b7a56" and "c26680de84eb50e1299219fee84278b06382429d" have entirely different histories.
a76a841238
...
c26680de84
|
@ -1,27 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.admin.statistics.member
|
|
||||||
|
|
||||||
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.RequestParam
|
|
||||||
import org.springframework.web.bind.annotation.RestController
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
|
||||||
@RequestMapping("/admin/member/statistics")
|
|
||||||
class AdminMemberStatisticsController(private val service: AdminMemberStatisticsService) {
|
|
||||||
@GetMapping
|
|
||||||
fun getStatistics(
|
|
||||||
@RequestParam startDateStr: String,
|
|
||||||
@RequestParam endDateStr: String,
|
|
||||||
pageable: Pageable
|
|
||||||
) = ApiResponse.ok(
|
|
||||||
service.getStatistics(
|
|
||||||
startDateStr = startDateStr,
|
|
||||||
endDateStr = endDateStr,
|
|
||||||
pageable = pageable
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,153 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.admin.statistics.member
|
|
||||||
|
|
||||||
import com.querydsl.core.types.dsl.DateTimePath
|
|
||||||
import com.querydsl.core.types.dsl.Expressions
|
|
||||||
import com.querydsl.core.types.dsl.StringTemplate
|
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
|
||||||
import kr.co.vividnext.sodalive.can.charge.ChargeStatus
|
|
||||||
import kr.co.vividnext.sodalive.can.charge.QCharge.charge
|
|
||||||
import kr.co.vividnext.sodalive.can.payment.PaymentStatus
|
|
||||||
import kr.co.vividnext.sodalive.can.payment.QPayment.payment
|
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
|
||||||
import kr.co.vividnext.sodalive.member.QSignOut.signOut
|
|
||||||
import org.springframework.stereotype.Repository
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
class AdminMemberStatisticsRepository(private val queryFactory: JPAQueryFactory) {
|
|
||||||
fun getTotalSignUpCount(startDate: LocalDateTime, endDate: LocalDateTime): Int {
|
|
||||||
return queryFactory
|
|
||||||
.select(member.id)
|
|
||||||
.from(member)
|
|
||||||
.where(
|
|
||||||
member.createdAt.goe(startDate),
|
|
||||||
member.createdAt.loe(endDate)
|
|
||||||
)
|
|
||||||
.fetch()
|
|
||||||
.size
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getTotalSignOutCount(startDate: LocalDateTime, endDate: LocalDateTime): Int {
|
|
||||||
return queryFactory
|
|
||||||
.select(signOut.id)
|
|
||||||
.from(signOut)
|
|
||||||
.where(
|
|
||||||
signOut.createdAt.goe(startDate),
|
|
||||||
signOut.createdAt.loe(endDate)
|
|
||||||
)
|
|
||||||
.fetch()
|
|
||||||
.size
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPaymentMemberCount(startDate: LocalDateTime, endDate: LocalDateTime): Int {
|
|
||||||
return queryFactory
|
|
||||||
.select(
|
|
||||||
QDateAndMemberCount(
|
|
||||||
getFormattedDate(charge.createdAt),
|
|
||||||
member.id.countDistinct().castToNum(Int::class.java)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.from(charge)
|
|
||||||
.innerJoin(charge.member, member)
|
|
||||||
.innerJoin(charge.payment, payment)
|
|
||||||
.where(
|
|
||||||
charge.status.eq(ChargeStatus.CHARGE),
|
|
||||||
payment.status.eq(PaymentStatus.COMPLETE),
|
|
||||||
charge.createdAt.goe(startDate),
|
|
||||||
charge.createdAt.loe(endDate)
|
|
||||||
)
|
|
||||||
.groupBy(getFormattedDate(charge.createdAt))
|
|
||||||
.fetch()
|
|
||||||
.sumOf { it.memberCount }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSignUpCountInRange(
|
|
||||||
startDate: LocalDateTime,
|
|
||||||
endDate: LocalDateTime,
|
|
||||||
offset: Long,
|
|
||||||
limit: Long
|
|
||||||
): List<DateAndMemberCount> {
|
|
||||||
return queryFactory
|
|
||||||
.select(
|
|
||||||
QDateAndMemberCount(
|
|
||||||
getFormattedDate(member.createdAt),
|
|
||||||
member.id.countDistinct().castToNum(Int::class.java)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.from(member)
|
|
||||||
.where(
|
|
||||||
member.createdAt.goe(startDate),
|
|
||||||
member.createdAt.loe(endDate)
|
|
||||||
)
|
|
||||||
.groupBy(getFormattedDate(member.createdAt))
|
|
||||||
.offset(offset)
|
|
||||||
.limit(limit)
|
|
||||||
.fetch()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSignOutCountInRange(
|
|
||||||
startDate: LocalDateTime,
|
|
||||||
endDate: LocalDateTime,
|
|
||||||
offset: Long,
|
|
||||||
limit: Long
|
|
||||||
): List<DateAndMemberCount> {
|
|
||||||
return queryFactory
|
|
||||||
.select(
|
|
||||||
QDateAndMemberCount(
|
|
||||||
getFormattedDate(signOut.createdAt),
|
|
||||||
signOut.id.countDistinct().castToNum(Int::class.java)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.from(signOut)
|
|
||||||
.where(
|
|
||||||
signOut.createdAt.goe(startDate),
|
|
||||||
signOut.createdAt.loe(endDate)
|
|
||||||
)
|
|
||||||
.groupBy(getFormattedDate(signOut.createdAt))
|
|
||||||
.offset(offset)
|
|
||||||
.limit(limit)
|
|
||||||
.fetch()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPaymentMemberCountInRange(
|
|
||||||
startDate: LocalDateTime,
|
|
||||||
endDate: LocalDateTime,
|
|
||||||
offset: Long,
|
|
||||||
limit: Long
|
|
||||||
): List<DateAndMemberCount> {
|
|
||||||
return queryFactory
|
|
||||||
.select(
|
|
||||||
QDateAndMemberCount(
|
|
||||||
getFormattedDate(charge.createdAt),
|
|
||||||
member.id.countDistinct().castToNum(Int::class.java)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.from(charge)
|
|
||||||
.innerJoin(charge.member, member)
|
|
||||||
.innerJoin(charge.payment, payment)
|
|
||||||
.where(
|
|
||||||
charge.status.eq(ChargeStatus.CHARGE),
|
|
||||||
payment.status.eq(PaymentStatus.COMPLETE),
|
|
||||||
charge.createdAt.goe(startDate),
|
|
||||||
charge.createdAt.loe(endDate)
|
|
||||||
)
|
|
||||||
.groupBy(getFormattedDate(charge.createdAt))
|
|
||||||
.offset(offset)
|
|
||||||
.limit(limit)
|
|
||||||
.fetch()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getFormattedDate(dateTimePath: DateTimePath<LocalDateTime>): 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"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,114 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.admin.statistics.member
|
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
|
||||||
import org.springframework.data.domain.Pageable
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.LocalTime
|
|
||||||
import java.time.ZoneId
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
import java.time.temporal.ChronoUnit
|
|
||||||
|
|
||||||
@Service
|
|
||||||
class AdminMemberStatisticsService(private val repository: AdminMemberStatisticsRepository) {
|
|
||||||
fun getStatistics(
|
|
||||||
startDateStr: String,
|
|
||||||
endDateStr: String,
|
|
||||||
pageable: Pageable
|
|
||||||
): GetMemberStatisticsResponse {
|
|
||||||
val dateRange = getPagedDateRange(
|
|
||||||
startDate = startDateStr,
|
|
||||||
endDate = endDateStr,
|
|
||||||
page = pageable.pageNumber + 1,
|
|
||||||
pageSize = pageable.pageSize
|
|
||||||
)
|
|
||||||
|
|
||||||
if (dateRange == null) {
|
|
||||||
throw SodaException("잘못된 접근입니다.")
|
|
||||||
}
|
|
||||||
|
|
||||||
var startDate = LocalDate.parse(startDateStr).atStartOfDay()
|
|
||||||
.atZone(ZoneId.of("Asia/Seoul"))
|
|
||||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
|
||||||
.toLocalDateTime()
|
|
||||||
|
|
||||||
var endDate = LocalDate.parse(endDateStr).atTime(LocalTime.MAX)
|
|
||||||
.atZone(ZoneId.of("Asia/Seoul"))
|
|
||||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
|
||||||
.toLocalDateTime()
|
|
||||||
|
|
||||||
val totalSignUpCount = repository.getTotalSignUpCount(startDate = startDate, endDate = endDate)
|
|
||||||
val totalSignOutCount = repository.getTotalSignOutCount(startDate = startDate, endDate = endDate)
|
|
||||||
val totalPaymentMemberCount = repository.getPaymentMemberCount(startDate = startDate, endDate = endDate)
|
|
||||||
|
|
||||||
startDate = dateRange.startDate
|
|
||||||
.atZone(ZoneId.of("Asia/Seoul"))
|
|
||||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
|
||||||
.toLocalDateTime()
|
|
||||||
|
|
||||||
endDate = dateRange.endDate
|
|
||||||
.atZone(ZoneId.of("Asia/Seoul"))
|
|
||||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
|
||||||
.toLocalDateTime()
|
|
||||||
|
|
||||||
val signUpCountInRange = repository.getSignUpCountInRange(
|
|
||||||
startDate = startDate,
|
|
||||||
endDate = endDate,
|
|
||||||
offset = pageable.offset,
|
|
||||||
limit = pageable.pageSize.toLong()
|
|
||||||
).associateBy({ it.date }, { it.memberCount })
|
|
||||||
|
|
||||||
val signOutCountInRange = repository.getSignOutCountInRange(
|
|
||||||
startDate = startDate,
|
|
||||||
endDate = endDate,
|
|
||||||
offset = pageable.offset,
|
|
||||||
limit = pageable.pageSize.toLong()
|
|
||||||
).associateBy({ it.date }, { it.memberCount })
|
|
||||||
|
|
||||||
val paymentMemberCountInRange = repository.getPaymentMemberCountInRange(
|
|
||||||
startDate = startDate,
|
|
||||||
endDate = endDate,
|
|
||||||
offset = pageable.offset,
|
|
||||||
limit = pageable.pageSize.toLong()
|
|
||||||
)
|
|
||||||
|
|
||||||
val paymentMemberCountInRangeMap = paymentMemberCountInRange.associateBy({ it.date }, { it.memberCount })
|
|
||||||
|
|
||||||
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
|
||||||
val items = generateSequence(dateRange.startDate) { it.plusDays(1) }
|
|
||||||
.takeWhile { !it.isAfter(dateRange.endDate) }
|
|
||||||
.map {
|
|
||||||
val date = it.format(formatter)
|
|
||||||
GetMemberStatisticsItem(
|
|
||||||
date = date,
|
|
||||||
signUpCount = signUpCountInRange[date] ?: 0,
|
|
||||||
signOutCount = signOutCountInRange[date] ?: 0,
|
|
||||||
paymentMemberCount = paymentMemberCountInRangeMap[date] ?: 0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.toList()
|
|
||||||
|
|
||||||
return GetMemberStatisticsResponse(
|
|
||||||
totalCount = dateRange.totalDays,
|
|
||||||
totalSignUpCount = totalSignUpCount,
|
|
||||||
totalSignOutCount = totalSignOutCount,
|
|
||||||
totalPaymentMemberCount = totalPaymentMemberCount,
|
|
||||||
items = items
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getPagedDateRange(startDate: String, endDate: String, page: Int, pageSize: Int): PagedDateRange? {
|
|
||||||
val start = LocalDate.parse(startDate)
|
|
||||||
val end = LocalDate.parse(endDate)
|
|
||||||
|
|
||||||
val totalDays = ChronoUnit.DAYS.between(start, end).toInt() + 1
|
|
||||||
val totalPages = (totalDays + pageSize - 1) / pageSize // 전체 페이지 개수 계산
|
|
||||||
|
|
||||||
if (page < 1 || page > totalPages) return null // 페이지 범위를 벗어나면 null 반환
|
|
||||||
|
|
||||||
val rangeStart = start.plusDays((page - 1) * pageSize.toLong()).atStartOfDay()
|
|
||||||
val rangeEnd = start.plusDays((page * pageSize - 1).toLong()).coerceAtMost(end).atTime(LocalTime.MAX)
|
|
||||||
|
|
||||||
return PagedDateRange(rangeStart, rangeEnd, totalDays)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.admin.statistics.member
|
|
||||||
|
|
||||||
import com.querydsl.core.annotations.QueryProjection
|
|
||||||
|
|
||||||
data class DateAndMemberCount @QueryProjection constructor(
|
|
||||||
val date: String,
|
|
||||||
val memberCount: Int
|
|
||||||
)
|
|
|
@ -1,16 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.admin.statistics.member
|
|
||||||
|
|
||||||
data class GetMemberStatisticsResponse(
|
|
||||||
val totalCount: Int,
|
|
||||||
val totalSignUpCount: Int,
|
|
||||||
val totalSignOutCount: Int,
|
|
||||||
val totalPaymentMemberCount: Int,
|
|
||||||
val items: List<GetMemberStatisticsItem>
|
|
||||||
)
|
|
||||||
|
|
||||||
data class GetMemberStatisticsItem(
|
|
||||||
val date: String,
|
|
||||||
val signUpCount: Int,
|
|
||||||
val signOutCount: Int,
|
|
||||||
val paymentMemberCount: Int
|
|
||||||
)
|
|
|
@ -1,9 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.admin.statistics.member
|
|
||||||
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
|
|
||||||
data class PagedDateRange(
|
|
||||||
val startDate: LocalDateTime,
|
|
||||||
val endDate: LocalDateTime,
|
|
||||||
val totalDays: Int
|
|
||||||
)
|
|
Loading…
Reference in New Issue