Files
36Wisdom/biz/service/redemption.go
T
2026-08-14 16:11:10 +08:00

158 lines
4.8 KiB
Go

package service
import (
"context"
"crypto/rand"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"36wisdom/biz/consts"
"36wisdom/biz/dao"
"36wisdom/biz/model/dto"
)
// ---------- 后台管理 ----------
type adminRedemption struct{}
var AdminRedemption = &adminRedemption{}
// List 兑换记录(status/prize_id 可选过滤),按创建时间倒序。
func (s *adminRedemption) List(ctx context.Context, req *dto.AdminRedemptionListReq) (*dto.AdminRedemptionListRes, error) {
recs, err := dao.Redemption.List(ctx, req.Status, req.PrizeId)
if err != nil {
return nil, err
}
childIds := make([]int64, 0, len(recs))
prizeIds := make([]int64, 0, len(recs))
for _, r := range recs {
childIds = append(childIds, r.UserId)
prizeIds = append(prizeIds, r.PrizeId)
}
nicknames, err := childNicknames(ctx, childIds)
if err != nil {
return nil, err
}
names, err := prizeNames(ctx, prizeIds)
if err != nil {
return nil, err
}
items := make([]*dto.AdminRedemptionItem, 0, len(recs))
for _, r := range recs {
items = append(items, &dto.AdminRedemptionItem{
Id: r.Id, ChildId: r.UserId, ChildNickname: nicknames[r.UserId],
PrizeId: r.PrizeId, PrizeName: names[r.PrizeId],
PointsCost: r.PointsCost, Status: r.Status,
Code: r.Code, CreatedAt: timeString(r.CreatedAt),
})
}
return &dto.AdminRedemptionListRes{List: items}, nil
}
// Ship 发货(待领取/待发货 → 已发货):未填兑换码则生成(前缀 + 8 位大写随机)。
func (s *adminRedemption) Ship(ctx context.Context, req *dto.AdminRedemptionShipReq) (*dto.AdminRedemptionShipRes, error) {
rec, err := dao.Redemption.GetByPk(ctx, req.Id)
if err != nil {
return nil, err
}
if rec == nil {
return nil, gerror.New("兑换记录不存在")
}
if rec.Status != consts.RedeemPending && rec.Status != consts.RedeemWaiting {
return nil, gerror.New("当前状态不可发货")
}
code := req.Code
if code == "" {
if code, err = genRedeemCode(); err != nil {
return nil, err
}
}
if err = dao.Redemption.UpdateByPk(ctx, req.Id, g.Map{
"status": consts.RedeemShipped, "code": code, "updated_at": gtime.Now(),
}); err != nil {
return nil, err
}
return &dto.AdminRedemptionShipRes{}, nil
}
// Receive 确认领取(已发货 → 已领取)。
func (s *adminRedemption) Receive(ctx context.Context, req *dto.AdminRedemptionReceiveReq) (*dto.AdminRedemptionReceiveRes, error) {
rec, err := dao.Redemption.GetByPk(ctx, req.Id)
if err != nil {
return nil, err
}
if rec == nil {
return nil, gerror.New("兑换记录不存在")
}
if rec.Status != consts.RedeemShipped {
return nil, gerror.New("仅已发货记录可确认领取")
}
if err = dao.Redemption.UpdateByPk(ctx, req.Id, g.Map{
"status": consts.RedeemReceived, "updated_at": gtime.Now(),
}); err != nil {
return nil, err
}
return &dto.AdminRedemptionReceiveRes{}, nil
}
// Cancel 取消兑换(待领取/待发货 → 已取消):事务内退回积分并记流水。
func (s *adminRedemption) Cancel(ctx context.Context, req *dto.AdminRedemptionCancelReq) (*dto.AdminRedemptionCancelRes, error) {
err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
rec, err := dao.Redemption.GetByPkInTx(ctx, tx, req.Id)
if err != nil {
return err
}
if rec == nil {
return gerror.New("兑换记录不存在")
}
if rec.Status != consts.RedeemPending && rec.Status != consts.RedeemWaiting {
return gerror.New("当前状态不可取消")
}
childId := rec.UserId
if _, err = tx.Model(consts.TableRedemption).Ctx(ctx).Data(g.Map{
"status": consts.RedeemCanceled, "updated_at": gtime.Now(),
}).WherePri(req.Id).Update(); err != nil {
return err
}
childRec, err := dao.Child.GetByPkInTx(ctx, tx, childId)
if err != nil {
return err
}
if childRec == nil {
return gerror.New("孩子不存在")
}
balance := childRec.Points + rec.PointsCost
if _, err = tx.Model(consts.TableChild).Ctx(ctx).Data(g.Map{"points": balance}).WherePri(childId).Update(); err != nil {
return err
}
_, err = tx.Model(consts.TablePointLog).Ctx(ctx).Data(g.Map{
"user_id": childId,
"change": rec.PointsCost,
"reason_type": consts.ReasonAdmin,
"ref_id": req.Id,
"balance_after": balance,
}).Insert()
return err
})
if err != nil {
return nil, err
}
return &dto.AdminRedemptionCancelRes{}, nil
}
// genRedeemCode 生成实物兑换码:前缀 + 8 位大写字母数字。
func genRedeemCode() (string, error) {
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
b := make([]byte, 8)
if _, err := rand.Read(b); err != nil {
return "", gerror.Wrap(err, "生成兑换码失败")
}
for i := range b {
b[i] = chars[int(b[i])%len(chars)]
}
return consts.RedeemCodePrefix + string(b), nil
}