138 lines
4.3 KiB
Go
138 lines
4.3 KiB
Go
package admin
|
|
|
|
import (
|
|
"math"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"bindbox-game/internal/code"
|
|
"bindbox-game/internal/pkg/core"
|
|
"bindbox-game/internal/repository/mysql/model"
|
|
)
|
|
|
|
type livestreamStatsResponse struct {
|
|
TotalRevenue int64 `json:"total_revenue"` // 总营收(分)
|
|
TotalRefund int64 `json:"total_refund"` // 总退款(分)
|
|
TotalCost int64 `json:"total_cost"` // 总成本(分)
|
|
NetProfit int64 `json:"net_profit"` // 净利润(分)
|
|
OrderCount int64 `json:"order_count"` // 订单数
|
|
RefundCount int64 `json:"refund_count"` // 退款数
|
|
ProfitMargin float64 `json:"profit_margin"` // 利润率 %
|
|
}
|
|
|
|
// GetLivestreamStats 获取直播间盈亏统计
|
|
// @Summary 获取直播间盈亏统计
|
|
// @Description 计算逻辑:净利润 = (营收 - 退款) - 奖品成本。营收 = 抽奖次数 * 门票价格。成本 = 中奖奖品成本总和。
|
|
// @Tags 管理端.直播间
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path integer true "活动ID"
|
|
// @Success 200 {object} livestreamStatsResponse
|
|
// @Failure 400 {object} code.Failure
|
|
// @Router /api/admin/livestream/activities/{id}/stats [get]
|
|
// @Security LoginVerifyToken
|
|
func (h *handler) GetLivestreamStats() core.HandlerFunc {
|
|
return func(ctx core.Context) {
|
|
activityID, err := strconv.ParseInt(ctx.Param("id"), 10, 64)
|
|
if err != nil || activityID <= 0 {
|
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "无效的活动ID"))
|
|
return
|
|
}
|
|
|
|
// 1. 获取活动信息(门票价格)
|
|
var activity model.LivestreamActivities
|
|
if err := h.repo.GetDbR().Where("id = ?", activityID).First(&activity).Error; err != nil {
|
|
ctx.AbortWithError(core.Error(http.StatusNotFound, code.ServerError, "活动不存在"))
|
|
return
|
|
}
|
|
ticketPrice := activity.TicketPrice
|
|
|
|
// 2. 从 livestream_draw_logs 统计抽奖次数
|
|
var drawLogs []model.LivestreamDrawLogs
|
|
if err := h.repo.GetDbR().Where("activity_id = ?", activityID).Find(&drawLogs).Error; err != nil {
|
|
ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error()))
|
|
return
|
|
}
|
|
|
|
orderCount := int64(len(drawLogs))
|
|
totalRevenue := orderCount * ticketPrice
|
|
|
|
// 3. 统计退款数量
|
|
var refundCount int64
|
|
h.repo.GetDbR().Model(&model.LivestreamDrawLogs{}).Where("activity_id = ? AND is_refunded = 1", activityID).Count(&refundCount)
|
|
totalRefund := refundCount * ticketPrice
|
|
|
|
// 4. 计算成本
|
|
prizeIDCountMap := make(map[int64]int64)
|
|
for _, log := range drawLogs {
|
|
prizeIDCountMap[log.PrizeID]++
|
|
}
|
|
|
|
prizeIDs := make([]int64, 0, len(prizeIDCountMap))
|
|
for pid := range prizeIDCountMap {
|
|
prizeIDs = append(prizeIDs, pid)
|
|
}
|
|
|
|
var totalCost int64
|
|
if len(prizeIDs) > 0 {
|
|
var prizes []model.LivestreamPrizes
|
|
h.repo.GetDbR().Where("id IN ?", prizeIDs).Find(&prizes)
|
|
|
|
prizeCostMap := make(map[int64]int64)
|
|
productIDsNeedingFallback := make([]int64, 0)
|
|
prizeProductMap := make(map[int64]int64)
|
|
|
|
for _, p := range prizes {
|
|
if p.CostPrice > 0 {
|
|
prizeCostMap[p.ID] = p.CostPrice
|
|
} else if p.ProductID > 0 {
|
|
productIDsNeedingFallback = append(productIDsNeedingFallback, p.ProductID)
|
|
prizeProductMap[p.ID] = p.ProductID
|
|
}
|
|
}
|
|
|
|
if len(productIDsNeedingFallback) > 0 {
|
|
var products []model.Products
|
|
h.repo.GetDbR().Where("id IN ?", productIDsNeedingFallback).Find(&products)
|
|
productPriceMap := make(map[int64]int64)
|
|
for _, prod := range products {
|
|
productPriceMap[prod.ID] = prod.Price
|
|
}
|
|
for prizeID, productID := range prizeProductMap {
|
|
if _, ok := prizeCostMap[prizeID]; !ok {
|
|
if price, found := productPriceMap[productID]; found {
|
|
prizeCostMap[prizeID] = price
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for prizeID, count := range prizeIDCountMap {
|
|
if cost, ok := prizeCostMap[prizeID]; ok {
|
|
totalCost += cost * count
|
|
}
|
|
}
|
|
}
|
|
|
|
netProfit := (totalRevenue - totalRefund) - totalCost
|
|
|
|
var margin float64
|
|
netRevenue := totalRevenue - totalRefund
|
|
if netRevenue > 0 {
|
|
margin = float64(netProfit) / float64(netRevenue) * 100
|
|
} else {
|
|
margin = -100
|
|
}
|
|
|
|
ctx.Payload(&livestreamStatsResponse{
|
|
TotalRevenue: totalRevenue,
|
|
TotalRefund: totalRefund,
|
|
TotalCost: totalCost,
|
|
NetProfit: netProfit,
|
|
OrderCount: orderCount,
|
|
RefundCount: refundCount,
|
|
ProfitMargin: math.Trunc(margin*100) / 100,
|
|
})
|
|
}
|
|
}
|