bindbox-game/internal/api/admin/ichiban_slots_admin.go
win 8d1eef2f7f fix(channel): 修复渠道统计GMV重复计数和商城直购误计入
1. 排除商城直购(source_type=1):GMV和成本过滤条件从IN(1,2,3,4)改为IN(2,3,4)
2. 排除次卡免费使用订单(actual_amount=0):避免购买次卡和使用次卡双重计入GMV
   - source_type=4 一番赏使用次卡:1578单 44032元重复
   - source_type=3 对对碰使用次卡:422单 7042元重复
   - 合计去除51074元虚增GMV(29.1%)
3. 成本过滤条件同步修正:source_type IN(2,3,4),total_amount>0

修正后:GMV从175600降至124527元,毛利率从37.4%回到真实的11.8%
2026-03-16 21:41:39 +08:00

110 lines
4.0 KiB
Go
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package admin
import (
"net/http"
"strconv"
"bindbox-game/internal/code"
"bindbox-game/internal/pkg/core"
"bindbox-game/internal/repository/mysql/dao"
activitysvc "bindbox-game/internal/service/activity"
)
type listSlotsRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
Claimed *bool `form:"claimed"`
}
type slotItem struct {
SlotIndex int64 `json:"slot_index"`
RewardID int64 `json:"reward_id"`
RewardName string `json:"reward_name"`
Level int32 `json:"level"`
ProductImage string `json:"product_image"`
Claimed bool `json:"claimed"`
}
type listSlotsResponse struct {
TotalSlots int64 `json:"total_slots"`
List []slotItem `json:"list"`
}
func (h *handler) ListIchibanSlots() core.HandlerFunc {
return func(ctx core.Context) {
req := new(listSlotsRequest)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, err.Error()))
return
}
activityID, err := strconv.ParseInt(ctx.Param("activity_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递活动ID"))
return
}
issueID, err := strconv.ParseInt(ctx.Param("issue_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递期ID"))
return
}
act, err := h.readDB.Activities.WithContext(ctx.RequestContext()).Where(h.readDB.Activities.ID.Eq(activityID)).First()
if err != nil || act == nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.GetActivityError, "活动不存在"))
return
}
if act.PlayType != "ichiban" {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 170201, "当前活动不支持一番赏查看"))
return
}
// 强一致以期次反查活动ID避免前端传参不一致导致承诺读取失败
is, _ := h.readDB.ActivityIssues.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityIssues.ID.Eq(issueID)).First()
if is == nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "期不存在"))
return
}
realActID := is.ActivityID
svc := activitysvc.NewIchibanSlotsService(dao.Use(h.repo.GetDbR()), h.repo)
total, items, err := svc.Page(ctx.RequestContext(), realActID, issueID, req.Page, req.PageSize, req.Claimed)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 170202, err.Error()))
return
}
out := make([]slotItem, len(items))
for i, it := range items {
out[i] = slotItem{SlotIndex: it.SlotIndex, RewardID: it.RewardID, RewardName: it.RewardName, Level: it.Level, ProductImage: it.ProductImage, Claimed: it.Claimed}
}
ctx.Payload(&listSlotsResponse{TotalSlots: total, List: out})
}
}
type slotDetailResponse struct {
Item slotItem `json:"item"`
}
func (h *handler) GetIchibanSlotDetail() core.HandlerFunc {
return func(ctx core.Context) {
issueID, err := strconv.ParseInt(ctx.Param("issue_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递期ID"))
return
}
slotIdx, err := strconv.ParseInt(ctx.Param("slot_index"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递位置"))
return
}
svc := activitysvc.NewIchibanSlotsService(dao.Use(h.repo.GetDbR()), h.repo)
is, _ := h.readDB.ActivityIssues.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityIssues.ID.Eq(issueID)).First()
if is == nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "期不存在"))
return
}
it, err := svc.SlotDetail(ctx.RequestContext(), is.ActivityID, issueID, slotIdx)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 170203, err.Error()))
return
}
ctx.Payload(&slotDetailResponse{Item: slotItem{SlotIndex: it.SlotIndex, RewardID: it.RewardID, RewardName: it.RewardName, Level: it.Level, ProductImage: it.ProductImage, Claimed: it.Claimed}})
}
}