1、历史汇总列表排序优化(按时间排序);
2、历史汇总(按小时)优化,支持不同产品筛选; 3、用户留存记录(按天)增加当月最后1天留存数据展示;
This commit is contained in:
parent
c818eef08e
commit
2863174547
|
@ -540,7 +540,7 @@ func (e MiGuDeployService) HistoricalSummaryListOld(c *gin.Context) {
|
||||||
`).
|
`).
|
||||||
Where("mg_order.subscribe_time BETWEEN ? AND ?", startTime, endTime).
|
Where("mg_order.subscribe_time BETWEEN ? AND ?", startTime, endTime).
|
||||||
Group("DATE(mg_order.subscribe_time), mg_order.product_id, mg_order.channel_code").
|
Group("DATE(mg_order.subscribe_time), mg_order.product_id, mg_order.channel_code").
|
||||||
Order("mg_order.product_id, mg_order.channel_code, mg_order.subscribe_time DESC")
|
Order("mg_order.subscribe_time DESC")
|
||||||
|
|
||||||
// 添加过滤条件
|
// 添加过滤条件
|
||||||
if req.SkuCode != 0 {
|
if req.SkuCode != 0 {
|
||||||
|
@ -1528,6 +1528,13 @@ func (e MiGuDeployService) UserDayRetentionList(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addData, err := models.GetMonthlyEffectiveUserStats(e.Orm, req.RetentionMonth, req.SkuCode, req.Channel)
|
||||||
|
if err != nil {
|
||||||
|
e.Logger.Error(err)
|
||||||
|
response.Error(c, http.StatusInternalServerError, err, "获取当月留存数据失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 计算下个月的1号(留存查询从下个月1号开始)
|
// 计算下个月的1号(留存查询从下个月1号开始)
|
||||||
parsedMonth, err := time.Parse("2006-01", req.RetentionMonth)
|
parsedMonth, err := time.Parse("2006-01", req.RetentionMonth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1725,6 +1732,13 @@ func (e MiGuDeployService) UserDayRetentionList(c *gin.Context) {
|
||||||
retentionData = append(retentionData, result)
|
retentionData = append(retentionData, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var addDayRetention models.MgUserDayRetention
|
||||||
|
addDayRetention.Date = addData.Date
|
||||||
|
addDayRetention.RetainedUserCount = addData.ValidUserCount
|
||||||
|
addDayRetention.RetentionRate = addData.EffectiveRate
|
||||||
|
addDayRetention.UserUnsubOnDay = int(addData.UnsubscribedToday)
|
||||||
|
retentionData = append(retentionData, addDayRetention)
|
||||||
|
|
||||||
// 排序(按日期升序)
|
// 排序(按日期升序)
|
||||||
sort.SliceStable(retentionData, func(i, j int) bool {
|
sort.SliceStable(retentionData, func(i, j int) bool {
|
||||||
return retentionData[i].Date > retentionData[j].Date
|
return retentionData[i].Date > retentionData[j].Date
|
||||||
|
@ -2952,148 +2966,152 @@ func (e MiGuDeployService) HourSummaryList(c *gin.Context) {
|
||||||
|
|
||||||
// 查询历史数据(按小时),并计算退订率和提交成功率
|
// 查询历史数据(按小时),并计算退订率和提交成功率
|
||||||
func (e MiGuDeployService) queryHistoricalDataByHour(startTime, endTime string, req *models.HistoricalSummaryListReq) ([]models.MgHourSummary, *models.MgHourSummary, int, error) {
|
func (e MiGuDeployService) queryHistoricalDataByHour(startTime, endTime string, req *models.HistoricalSummaryListReq) ([]models.MgHourSummary, *models.MgHourSummary, int, error) {
|
||||||
// 构建SQL查询字符串
|
// 动态构建产品和渠道过滤条件
|
||||||
sql := `
|
var filterConds string
|
||||||
SELECT * FROM (
|
var args []interface{}
|
||||||
SELECT
|
args = append(args, startTime, endTime)
|
||||||
hour,
|
|
||||||
product_id,
|
|
||||||
channel_code,
|
|
||||||
SUM(new_user_count) AS new_user_count,
|
|
||||||
SUM(new_user_unsub_within_hour) AS new_user_unsub_within_hour,
|
|
||||||
SUM(new_user_unsub_on_day) AS new_user_unsub_on_day,
|
|
||||||
SUM(total_new_user_unsub) AS total_new_user_unsub,
|
|
||||||
SUM(submission_count) AS submission_count,
|
|
||||||
IF(SUM(submission_count) > 0,
|
|
||||||
CONCAT(ROUND(SUM(new_user_count) / SUM(submission_count) * 100, 2), '%'),
|
|
||||||
'0.00%'
|
|
||||||
) AS submission_success_rate,
|
|
||||||
IF(SUM(new_user_count) > 0,
|
|
||||||
CONCAT(ROUND(SUM(new_user_unsub_within_hour) / SUM(new_user_count) * 100, 2), '%'),
|
|
||||||
'0.00%'
|
|
||||||
) AS new_user_unsub_within_hour_rate,
|
|
||||||
IF(SUM(new_user_count) > 0,
|
|
||||||
CONCAT(ROUND(SUM(new_user_unsub_on_day) / SUM(new_user_count) * 100, 2), '%'),
|
|
||||||
'0.00%'
|
|
||||||
) AS new_user_unsub_on_day_rate,
|
|
||||||
IF(SUM(new_user_count) > 0,
|
|
||||||
CONCAT(ROUND(SUM(total_new_user_unsub) / SUM(new_user_count) * 100, 2), '%'),
|
|
||||||
'0.00%'
|
|
||||||
) AS total_new_user_unsub_rate
|
|
||||||
FROM (
|
|
||||||
-- 第一个查询:mg_order 数据
|
|
||||||
SELECT
|
|
||||||
HOUR(mg_order.created_at) AS hour,
|
|
||||||
product_id,
|
|
||||||
channel_code,
|
|
||||||
COUNT(*) AS new_user_count,
|
|
||||||
SUM(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 ELSE 0 END) AS new_user_unsub_within_hour,
|
|
||||||
SUM(CASE WHEN mg_order.state = 2 AND DATE(mg_order.unsubscribe_time) = DATE(mg_order.subscribe_time) THEN 1 ELSE 0 END) AS new_user_unsub_on_day,
|
|
||||||
SUM(CASE WHEN mg_order.state = 2 THEN 1 ELSE 0 END) AS total_new_user_unsub,
|
|
||||||
0 AS submission_count
|
|
||||||
FROM
|
|
||||||
mg_order
|
|
||||||
WHERE
|
|
||||||
mg_order.created_at BETWEEN ? AND ?
|
|
||||||
GROUP BY
|
|
||||||
HOUR(mg_order.created_at)
|
|
||||||
UNION ALL
|
|
||||||
-- 第二个查询:mg_order 总计数据
|
|
||||||
SELECT
|
|
||||||
'Total' AS hour,
|
|
||||||
product_id,
|
|
||||||
channel_code,
|
|
||||||
COUNT(*) AS new_user_count,
|
|
||||||
SUM(CASE WHEN mg_order.is_one_hour_cancel = 1 THEN 1 ELSE 0 END) AS new_user_unsub_within_hour,
|
|
||||||
SUM(CASE WHEN mg_order.state = 2 AND DATE(mg_order.unsubscribe_time) = DATE(mg_order.subscribe_time) THEN 1 ELSE 0 END) AS new_user_unsub_on_day,
|
|
||||||
SUM(CASE WHEN mg_order.state = 2 THEN 1 ELSE 0 END) AS total_new_user_unsub,
|
|
||||||
0 AS submission_count
|
|
||||||
FROM
|
|
||||||
mg_order
|
|
||||||
WHERE
|
|
||||||
mg_order.created_at BETWEEN ? AND ?
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
-- 第三个查询:mg_transaction_log 数据
|
|
||||||
SELECT
|
|
||||||
HOUR(mg_transaction_log.created_at) AS hour,
|
|
||||||
product_id AS product_id,
|
|
||||||
channel_code AS channel_code,
|
|
||||||
0 AS new_user_count,
|
|
||||||
0 AS new_user_unsub_within_hour,
|
|
||||||
0 AS new_user_unsub_on_day,
|
|
||||||
0 AS total_new_user_unsub,
|
|
||||||
COUNT(*) AS submission_count
|
|
||||||
FROM
|
|
||||||
mg_transaction_log
|
|
||||||
WHERE
|
|
||||||
mg_transaction_log.created_at BETWEEN ? AND ?
|
|
||||||
AND verification_code != ''
|
|
||||||
GROUP BY
|
|
||||||
HOUR(mg_transaction_log.created_at)
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
-- 第四个查询:mg_transaction_log 总计数据
|
|
||||||
SELECT
|
|
||||||
'Total' AS hour,
|
|
||||||
product_id AS product_id,
|
|
||||||
channel_code AS channel_code,
|
|
||||||
0 AS new_user_count,
|
|
||||||
0 AS new_user_unsub_within_hour,
|
|
||||||
0 AS new_user_unsub_on_day,
|
|
||||||
0 AS total_new_user_unsub,
|
|
||||||
COUNT(*) AS submission_count
|
|
||||||
FROM
|
|
||||||
mg_transaction_log
|
|
||||||
WHERE
|
|
||||||
mg_transaction_log.created_at BETWEEN ? AND ?
|
|
||||||
AND verification_code != ''
|
|
||||||
) AS combined_data
|
|
||||||
GROUP BY hour
|
|
||||||
ORDER BY hour
|
|
||||||
) AS paginated_data;
|
|
||||||
`
|
|
||||||
|
|
||||||
// 执行查询
|
if req.SkuCode > 0 {
|
||||||
|
filterConds += " AND product_id = ?"
|
||||||
|
args = append(args, req.SkuCode)
|
||||||
|
}
|
||||||
|
if req.Channel != "" {
|
||||||
|
filterConds += " AND channel_code = ?"
|
||||||
|
args = append(args, req.Channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拷贝参数用于不同的 UNION 查询
|
||||||
|
argsOrder := append([]interface{}{}, args...)
|
||||||
|
argsOrderTotal := append([]interface{}{}, args...)
|
||||||
|
argsLog := append([]interface{}{}, args...)
|
||||||
|
argsLogTotal := append([]interface{}{}, args...)
|
||||||
|
|
||||||
|
// 构建 SQL 查询
|
||||||
|
sql := fmt.Sprintf(`
|
||||||
|
SELECT * FROM (
|
||||||
|
SELECT
|
||||||
|
hour,
|
||||||
|
product_id,
|
||||||
|
channel_code,
|
||||||
|
SUM(new_user_count) AS new_user_count,
|
||||||
|
SUM(new_user_unsub_within_hour) AS new_user_unsub_within_hour,
|
||||||
|
SUM(new_user_unsub_on_day) AS new_user_unsub_on_day,
|
||||||
|
SUM(total_new_user_unsub) AS total_new_user_unsub,
|
||||||
|
SUM(submission_count) AS submission_count,
|
||||||
|
IF(SUM(submission_count) > 0,
|
||||||
|
CONCAT(ROUND(SUM(new_user_count) / SUM(submission_count) * 100, 2), '%%'),
|
||||||
|
'0.00%%'
|
||||||
|
) AS submission_success_rate,
|
||||||
|
IF(SUM(new_user_count) > 0,
|
||||||
|
CONCAT(ROUND(SUM(new_user_unsub_within_hour) / SUM(new_user_count) * 100, 2), '%%'),
|
||||||
|
'0.00%%'
|
||||||
|
) AS new_user_unsub_within_hour_rate,
|
||||||
|
IF(SUM(new_user_count) > 0,
|
||||||
|
CONCAT(ROUND(SUM(new_user_unsub_on_day) / SUM(new_user_count) * 100, 2), '%%'),
|
||||||
|
'0.00%%'
|
||||||
|
) AS new_user_unsub_on_day_rate,
|
||||||
|
IF(SUM(new_user_count) > 0,
|
||||||
|
CONCAT(ROUND(SUM(total_new_user_unsub) / SUM(new_user_count) * 100, 2), '%%'),
|
||||||
|
'0.00%%'
|
||||||
|
) AS total_new_user_unsub_rate
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
HOUR(subscribe_time) AS hour,
|
||||||
|
product_id,
|
||||||
|
channel_code,
|
||||||
|
COUNT(*) AS new_user_count,
|
||||||
|
SUM(CASE WHEN is_one_hour_cancel = 1 THEN 1 ELSE 0 END) AS new_user_unsub_within_hour,
|
||||||
|
SUM(CASE WHEN state = 2 AND DATE(unsubscribe_time) = DATE(subscribe_time) THEN 1 ELSE 0 END) AS new_user_unsub_on_day,
|
||||||
|
SUM(CASE WHEN state = 2 THEN 1 ELSE 0 END) AS total_new_user_unsub,
|
||||||
|
0 AS submission_count
|
||||||
|
FROM mg_order
|
||||||
|
WHERE subscribe_time BETWEEN ? AND ? %s
|
||||||
|
GROUP BY HOUR(subscribe_time), channel_code, product_id -- 按小时和渠道分组
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'Total' AS hour,
|
||||||
|
product_id,
|
||||||
|
channel_code,
|
||||||
|
COUNT(*) AS new_user_count,
|
||||||
|
SUM(CASE WHEN is_one_hour_cancel = 1 THEN 1 ELSE 0 END),
|
||||||
|
SUM(CASE WHEN state = 2 AND DATE(unsubscribe_time) = DATE(subscribe_time) THEN 1 ELSE 0 END),
|
||||||
|
SUM(CASE WHEN state = 2 THEN 1 ELSE 0 END),
|
||||||
|
0
|
||||||
|
FROM mg_order
|
||||||
|
WHERE subscribe_time BETWEEN ? AND ? %s
|
||||||
|
GROUP BY channel_code, product_id -- 按渠道和产品分组
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
HOUR(created_at) AS hour,
|
||||||
|
product_id,
|
||||||
|
channel_code,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
COUNT(*) AS submission_count
|
||||||
|
FROM mg_transaction_log
|
||||||
|
WHERE created_at BETWEEN ? AND ? AND verification_code != '' %s
|
||||||
|
GROUP BY HOUR(created_at), channel_code -- 按小时和渠道分组
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'Total' AS hour,
|
||||||
|
product_id,
|
||||||
|
channel_code,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
COUNT(*) AS submission_count
|
||||||
|
FROM mg_transaction_log
|
||||||
|
WHERE created_at BETWEEN ? AND ? AND verification_code != '' %s
|
||||||
|
GROUP BY channel_code, product_id -- 按渠道和产品分组
|
||||||
|
) AS combined_data
|
||||||
|
GROUP BY hour, channel_code -- 按小时和渠道分组
|
||||||
|
ORDER BY hour
|
||||||
|
) AS paginated_data
|
||||||
|
`, filterConds, filterConds, filterConds, filterConds)
|
||||||
|
|
||||||
|
// 整合参数
|
||||||
|
args = append(argsOrder, argsOrderTotal...)
|
||||||
|
args = append(args, argsLog...)
|
||||||
|
args = append(args, argsLogTotal...)
|
||||||
|
|
||||||
|
// 查询数据
|
||||||
var data []models.MgHourSummary
|
var data []models.MgHourSummary
|
||||||
err := e.Orm.Raw(sql, startTime, endTime, startTime, endTime, startTime, endTime, startTime, endTime).Scan(&data).Error
|
err := e.Orm.Raw(sql, args...).Scan(&data).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 剔除 "Total" 数据
|
// 拆分小时数据与汇总数据
|
||||||
var filteredData []models.MgHourSummary
|
var filteredData []models.MgHourSummary
|
||||||
var summaryData *models.MgHourSummary
|
var summaryData *models.MgHourSummary
|
||||||
for _, item := range data {
|
for _, item := range data {
|
||||||
if item.Hour == "Total" {
|
if item.Hour == "Total" {
|
||||||
summaryData = &item // 保存汇总数据
|
summaryData = &item
|
||||||
} else {
|
} else {
|
||||||
filteredData = append(filteredData, item) // 只保留小时数据
|
filteredData = append(filteredData, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按小时排序(确保 hour 是数字,排序正确)
|
// 按小时降序排序
|
||||||
sort.Slice(filteredData, func(i, j int) bool {
|
sort.Slice(filteredData, func(i, j int) bool {
|
||||||
// 将字符串类型的 hour 转换为整数进行比较
|
|
||||||
hourI, errI := strconv.Atoi(filteredData[i].Hour)
|
hourI, errI := strconv.Atoi(filteredData[i].Hour)
|
||||||
hourJ, errJ := strconv.Atoi(filteredData[j].Hour)
|
hourJ, errJ := strconv.Atoi(filteredData[j].Hour)
|
||||||
|
|
||||||
// 如果转换失败,按原样比较;否则按数字顺序排序
|
|
||||||
if errI != nil && errJ != nil {
|
if errI != nil && errJ != nil {
|
||||||
return filteredData[i].Hour > filteredData[j].Hour
|
return filteredData[i].Hour > filteredData[j].Hour
|
||||||
}
|
}
|
||||||
if errI != nil {
|
if errI != nil {
|
||||||
// 如果 i 的 hour 无法转换为数字,则认为它大于 j
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if errJ != nil {
|
if errJ != nil {
|
||||||
// 如果 j 的 hour 无法转换为数字,则认为它大于 i
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return hourI > hourJ
|
return hourI > hourJ
|
||||||
})
|
})
|
||||||
|
|
||||||
// 如果是导出数据,直接返回所有数据
|
// 导出模式:不分页
|
||||||
if req.IsExport == 1 {
|
if req.IsExport == 1 {
|
||||||
return filteredData, summaryData, 0, nil
|
return filteredData, summaryData, 0, nil
|
||||||
}
|
}
|
||||||
|
@ -3101,14 +3119,13 @@ func (e MiGuDeployService) queryHistoricalDataByHour(startTime, endTime string,
|
||||||
// 分页处理
|
// 分页处理
|
||||||
start := (req.PageNum - 1) * req.PageSize
|
start := (req.PageNum - 1) * req.PageSize
|
||||||
end := start + req.PageSize
|
end := start + req.PageSize
|
||||||
if end > len(filteredData) {
|
|
||||||
end = len(filteredData)
|
|
||||||
}
|
|
||||||
if start > len(filteredData) {
|
if start > len(filteredData) {
|
||||||
start = 0
|
start = 0
|
||||||
}
|
}
|
||||||
|
if end > len(filteredData) {
|
||||||
|
end = len(filteredData)
|
||||||
|
}
|
||||||
paginatedData := filteredData[start:end]
|
paginatedData := filteredData[start:end]
|
||||||
|
|
||||||
// 返回分页数据
|
|
||||||
return paginatedData, summaryData, len(filteredData), nil
|
return paginatedData, summaryData, len(filteredData), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2058,3 +2058,79 @@ func ExportTransactionToExcel(data []MgTransactionLog, db *gorm.DB) (string, err
|
||||||
|
|
||||||
return url, nil
|
return url, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MonthlyEffectiveUserStats struct {
|
||||||
|
Date string `json:"date"` // 留存日期(格式:YYYY-MM-DD)
|
||||||
|
NewUserCount int `json:"new_user_count"`
|
||||||
|
ValidUserCount int `json:"valid_user_count"`
|
||||||
|
EffectiveRate string `json:"effective_rate"` // 百分比格式:如 "85.63%"
|
||||||
|
UnsubscribedToday int64 `json:"unsubscribed_today"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMonthlyEffectiveUserStats 获取某月份有效用户统计
|
||||||
|
func GetMonthlyEffectiveUserStats(db *gorm.DB, retentionMonth string, skuCode int, channelCode string) (*MonthlyEffectiveUserStats, error) {
|
||||||
|
var stats MonthlyEffectiveUserStats
|
||||||
|
|
||||||
|
// 解析月份时间范围
|
||||||
|
startTime, err := time.Parse("2006-01", retentionMonth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid retentionMonth format: %v", err)
|
||||||
|
}
|
||||||
|
endTime := startTime.AddDate(0, 1, 0) // 下个月
|
||||||
|
lastDay := endTime.AddDate(0, 0, -1) // 当前月的最后一天
|
||||||
|
lastDayStr := lastDay.Format("2006-01-02") // 格式化为字符串
|
||||||
|
|
||||||
|
// 设置返回的留存日期字段
|
||||||
|
stats.Date = lastDayStr
|
||||||
|
|
||||||
|
// 查询每月新用户数和有效用户数
|
||||||
|
var monthlyData []struct {
|
||||||
|
Month string
|
||||||
|
NewUserCount int
|
||||||
|
ValidUsersCount int
|
||||||
|
}
|
||||||
|
err = db.Model(&MgOrder{}).
|
||||||
|
Select(`DATE_FORMAT(subscribe_time, '%Y-%m') AS month,
|
||||||
|
COUNT(DISTINCT phone_number) AS new_user_count,
|
||||||
|
COUNT(DISTINCT CASE
|
||||||
|
WHEN unsubscribe_time IS NULL OR TIMESTAMPDIFF(HOUR, subscribe_time, unsubscribe_time) > 24
|
||||||
|
THEN phone_number END) AS valid_users_count`).
|
||||||
|
Where("product_id = ?", skuCode).
|
||||||
|
Where("channel_code = ?", channelCode).
|
||||||
|
Group("month").
|
||||||
|
Order("month").
|
||||||
|
Find(&monthlyData).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 匹配当前月份的数据
|
||||||
|
for _, data := range monthlyData {
|
||||||
|
if data.Month == retentionMonth {
|
||||||
|
stats.NewUserCount = data.NewUserCount
|
||||||
|
stats.ValidUserCount = data.ValidUsersCount
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算有效用户率
|
||||||
|
if stats.NewUserCount > 0 {
|
||||||
|
rate := float64(stats.ValidUserCount) / float64(stats.NewUserCount) * 100
|
||||||
|
stats.EffectiveRate = fmt.Sprintf("%.2f%%", rate)
|
||||||
|
} else {
|
||||||
|
stats.EffectiveRate = "0.00%"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询今天的退订用户数
|
||||||
|
err = db.Model(&MgOrder{}).
|
||||||
|
Where("unsubscribe_time >= ? AND unsubscribe_time <= ?", lastDayStr+" 00:00:00", lastDayStr+" 23:59:59").
|
||||||
|
Where("state = 2").
|
||||||
|
Where("product_id = ?", skuCode).
|
||||||
|
Where("channel_code = ?", channelCode).
|
||||||
|
Count(&stats.UnsubscribedToday).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &stats, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user