bindbox-game/internal/api/user/coupons_usage_app.go
邹方成 45815bfb7d chore: 清理无用文件与优化代码结构
refactor(utils): 修复密码哈希比较逻辑错误
feat(user): 新增按状态筛选优惠券接口
docs: 添加虚拟发货与任务中心相关文档
fix(wechat): 修正Code2Session上下文传递问题
test: 补充订单折扣与积分转换测试用例
build: 更新配置文件与构建脚本
style: 清理多余的空行与注释
2025-12-18 17:35:55 +08:00

201 lines
6.1 KiB
Go
Raw Permalink 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 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)
}
}