package app import ( "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" "bindbox-game/internal/pkg/validation" "bindbox-game/internal/repository/mysql/model" "net/http" "strconv" "strings" ) type listCouponUsageRequest struct { Page int `form:"page"` PageSize int `form:"page_size"` } // 优惠券详情 type couponDetail struct { ID int64 `json:"id"` Name string `json:"name"` Amount int64 `json:"amount"` // 原始面值 Remaining int64 `json:"remaining"` // 剩余额度 UsedAmount int64 `json:"used_amount"` // 累计已使用金额 UseCount int64 `json:"use_count"` // 使用次数 ValidStart string `json:"valid_start"` ValidEnd string `json:"valid_end"` Status int32 `json:"status"` } // 使用记录项 type couponUsageItem struct { ID int64 `json:"id"` OrderNo string `json:"order_no"` // 订单号 ActivityName string `json:"activity_name"` // 活动名称 Amount int64 `json:"amount"` // 本次使用金额(正数) CreatedAt string `json:"created_at"` } type listCouponUsageResponse struct { Coupon *couponDetail `json:"coupon"` Records []couponUsageItem `json:"records"` Total int64 `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` } // ListUserCouponUsage 获取单张优惠券使用记录 // @Summary 获取单张优惠券使用记录 // @Description 获取指定优惠券的使用记录,包含优惠券详情、订单号、活动名称 // @Tags APP端.用户 // @Accept json // @Produce json // @Param user_id path integer true "用户ID" // @Param user_coupon_id path integer true "用户优惠券ID" // @Security LoginVerifyToken // @Param page query int false "页码,默认1" // @Param page_size query int false "每页数量,默认20" // @Success 200 {object} listCouponUsageResponse // @Failure 400 {object} code.Failure // @Router /api/app/users/{user_id}/coupons/{user_coupon_id}/usage [get] func (h *handler) ListUserCouponUsage() core.HandlerFunc { return func(ctx core.Context) { req := new(listCouponUsageRequest) rsp := new(listCouponUsageResponse) if err := ctx.ShouldBindForm(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) return } if req.Page <= 0 { req.Page = 1 } if req.PageSize <= 0 { req.PageSize = 20 } if req.PageSize > 100 { req.PageSize = 100 } userID := int64(ctx.SessionUserInfo().Id) ucid, err := strconv.ParseInt(ctx.Param("user_coupon_id"), 10, 64) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递持券ID")) return } // 获取优惠券信息 uc, err := h.readDB.UserCoupons.WithContext(ctx.RequestContext()).ReadDB(). Where(h.readDB.UserCoupons.ID.Eq(ucid), h.readDB.UserCoupons.UserID.Eq(userID)).First() if err != nil || uc == nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, 10010, "优惠券不存在")) return } // 获取优惠券模板信息 sc, _ := h.readDB.SystemCoupons.WithContext(ctx.RequestContext()).ReadDB(). Where(h.readDB.SystemCoupons.ID.Eq(uc.CouponID)).First() // 统计使用次数和累计使用金额 var useCount int64 var usedAmount int64 _ = h.repo.GetDbR().Raw("SELECT COUNT(*), COALESCE(SUM(ABS(change_amount)), 0) FROM user_coupon_ledger WHERE user_coupon_id = ? AND change_amount < 0", ucid).Row().Scan(&useCount, &usedAmount) // 构建优惠券详情 couponName := "" originalAmount := int64(0) if sc != nil { couponName = sc.Name originalAmount = sc.DiscountValue } rsp.Coupon = &couponDetail{ ID: uc.ID, Name: couponName, Amount: originalAmount, Remaining: uc.BalanceAmount, UsedAmount: usedAmount, UseCount: useCount, ValidStart: uc.ValidStart.Format("2006-01-02 15:04:05"), ValidEnd: func() string { if !uc.ValidEnd.IsZero() { return uc.ValidEnd.Format("2006-01-02 15:04:05") } return "" }(), Status: uc.Status, } // 获取使用记录 var total int64 db := h.repo.GetDbR().Model(&model.UserCouponLedger{}).Where("user_id = ? AND user_coupon_id = ? AND change_amount < 0", userID, ucid) _ = db.Count(&total).Error var list []model.UserCouponLedger _ = db.Order("id DESC").Offset((req.Page - 1) * req.PageSize).Limit(req.PageSize).Find(&list).Error // 收集订单ID批量查询 orderIDs := make([]int64, 0, len(list)) for _, v := range list { if v.OrderID > 0 { orderIDs = append(orderIDs, v.OrderID) } } // 批量查询订单信息 orderMap := make(map[int64]*model.Orders) if len(orderIDs) > 0 { orders, _ := h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB(). Where(h.readDB.Orders.ID.In(orderIDs...)).Find() for _, o := range orders { orderMap[o.ID] = o } } records := make([]couponUsageItem, len(list)) for i, v := range list { orderNo := "" activityName := "" if order, ok := orderMap[v.OrderID]; ok && order != nil { orderNo = order.OrderNo // 从 remark 解析活动信息 if order.SourceType == 2 && strings.Contains(order.Remark, "lottery:activity:") { // 尝试获取活动名称 remark := order.Remark var activityID int64 for _, seg := range strings.Split(remark, "|") { if strings.HasPrefix(seg, "lottery:activity:") { idStr := strings.TrimPrefix(seg, "lottery:activity:") if idx := strings.Index(idStr, ":"); idx > 0 { idStr = idStr[:idx] } activityID, _ = strconv.ParseInt(idStr, 10, 64) break } } if activityID > 0 { if act, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).ReadDB(). Where(h.readDB.Activities.ID.Eq(activityID)).First(); act != nil { activityName = act.Name } } } } records[i] = couponUsageItem{ ID: v.ID, OrderNo: orderNo, ActivityName: activityName, Amount: -v.ChangeAmount, // 转为正数 CreatedAt: v.CreatedAt.Format("2006-01-02 15:04:05"), } } rsp.Records = records rsp.Total = total rsp.Page = req.Page rsp.PageSize = req.PageSize ctx.Payload(rsp) } }