bindbox-game/internal/api/user/coupons_app.go

192 lines
5.5 KiB
Go
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 app
import (
"fmt"
"net/http"
"bindbox-game/internal/code"
"bindbox-game/internal/pkg/core"
"bindbox-game/internal/pkg/validation"
"bindbox-game/internal/repository/mysql/model"
)
type listCouponsRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
Status *int32 `form:"status"` // 1有效 2已失效不传默认1
}
type listCouponsResponse struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
List []couponItem `json:"list"`
}
type couponItem struct {
ID int64 `json:"id"`
Name string `json:"name"`
Amount int64 `json:"amount"`
Remaining int64 `json:"remaining"`
ValidStart string `json:"valid_start"`
ValidEnd string `json:"valid_end"`
Status int32 `json:"status"`
SubStatus string `json:"sub_status"` // 子状态unused/in_use/depleted/used/expired
StatusDesc string `json:"status_desc"` // 状态描述
Rules string `json:"rules"`
UsedAt string `json:"used_at,omitempty"` // 使用时间(已使用时返回)
UsedAmount int64 `json:"used_amount"` // 已使用金额
}
// ListUserCoupons 查看用户优惠券
// @Summary 查看用户优惠券
// @Description 查看用户持有的优惠券列表,支持按状态过滤
// @Tags APP端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Security LoginVerifyToken
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量最多100" default(20)
// @Param status query int false "状态1有效 2已失效不传默认1"
// @Success 200 {object} listCouponsResponse
// @Failure 400 {object} code.Failure
// @Router /api/app/users/{user_id}/coupons [get]
func (h *handler) ListUserCoupons() core.HandlerFunc {
return func(ctx core.Context) {
req := new(listCouponsRequest)
rsp := new(listCouponsResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
userID := int64(ctx.SessionUserInfo().Id)
// 状态1未使用 2已使用 3已过期
status := int32(1)
if req.Status != nil {
status = *req.Status
}
items, total, err := h.user.ListAppCoupons(ctx.RequestContext(), userID, status, req.Page, req.PageSize)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 10003, err.Error()))
return
}
rsp.Page = req.Page
rsp.PageSize = req.PageSize
rsp.Total = total
if len(items) == 0 {
rsp.List = []couponItem{}
ctx.Payload(rsp)
return
}
ids := make([]int64, 0, len(items))
for _, it := range items {
ids = append(ids, it.CouponID)
}
rows, err := h.readDB.SystemCoupons.WithContext(ctx.RequestContext()).
Where(h.readDB.SystemCoupons.ID.In(ids...)).
Find()
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, 10003, err.Error()))
return
}
mp := make(map[int64]*model.SystemCoupons, len(rows))
for i := range rows {
c := rows[i]
mp[c.ID] = c
}
rsp.List = make([]couponItem, 0, len(items))
for _, it := range items {
sc := mp[it.CouponID]
if sc == nil {
continue
}
name := sc.Name
amount := sc.DiscountValue
remaining := it.BalanceAmount
rules := buildCouponRules(sc)
vs := it.ValidStart.Format("2006-01-02 15:04:05")
ve := ""
if !it.ValidEnd.IsZero() {
ve = it.ValidEnd.Format("2006-01-02 15:04:05")
}
usedAt := ""
if !it.UsedAt.IsZero() {
usedAt = it.UsedAt.Format("2006-01-02 15:04:05")
}
usedAmount := amount - remaining
if usedAmount < 0 {
usedAmount = 0
}
// 计算子状态和描述
subStatus, statusDesc := calcCouponSubStatus(it, sc)
vi := couponItem{
ID: it.ID,
Name: name,
Amount: amount,
Remaining: remaining,
UsedAmount: usedAmount,
ValidStart: vs,
ValidEnd: ve,
Status: it.Status,
SubStatus: subStatus,
StatusDesc: statusDesc,
Rules: rules,
UsedAt: usedAt,
}
rsp.List = append(rsp.List, vi)
}
ctx.Payload(rsp)
}
}
func buildCouponRules(c *model.SystemCoupons) string {
switch c.DiscountType {
case 1:
return fmt.Sprintf("直减%v分满%v分可用", c.DiscountValue, c.MinSpend)
case 2:
return fmt.Sprintf("满%v分减%v分", c.MinSpend, c.DiscountValue)
case 3:
p := float64(c.DiscountValue) / 10.0
return fmt.Sprintf("折扣%0.1f%%,满%v分可用", p, c.MinSpend)
default:
return ""
}
}
// calcCouponSubStatus 计算优惠券的子状态和描述
// 子状态unused(未使用) in_use(使用中) depleted(用完) used(已使用) expired(已过期)
func calcCouponSubStatus(uc *model.UserCoupons, sc *model.SystemCoupons) (subStatus string, statusDesc string) {
amount := sc.DiscountValue
switch uc.Status {
case 1: // 数据库 status=1
if uc.BalanceAmount <= 0 {
// 余额=0 → 用完了
return "depleted", "已用完"
}
if sc.DiscountType == 1 && uc.BalanceAmount < amount {
// 金额券且余额 < 面值 → 使用中
return "in_use", "使用中"
}
return "unused", "可用"
case 2: // 数据库 status=2 → 已使用(满减/折扣券核销)
if sc.DiscountType == 1 {
return "depleted", "已用完"
}
return "used", "已使用"
case 3: // 数据库 status=3 → 已过期
return "expired", "已过期"
case 4: // 数据库 status=4 → 占用中(预扣),视为使用中
return "in_use", "使用中"
default:
return "unused", "可用"
}
}