158 lines
4.8 KiB
Go
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
|
|
}
|