Files
team-api/internal/logic/admin/redemption.go
T

246 lines
7.1 KiB
Go

package admin
import (
"context"
"crypto/rand"
"fmt"
do "github.com/qianfree/team-api/internal/model/do"
"math/big"
"strings"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
v1 "github.com/qianfree/team-api/api/admin/v1"
"github.com/qianfree/team-api/internal/dao"
"github.com/qianfree/team-api/internal/logic/common"
"github.com/qianfree/team-api/internal/utility/export"
)
// ListRedemptions 获取兑换码列表
func (s *sAdmin) ListRedemptions(ctx context.Context, req *v1.RedemptionListReq) (*v1.RedemptionListRes, error) {
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
query := dao.OrdRedemptions.Ctx(ctx)
if req.Status != "" {
query = query.Where("status", req.Status)
}
var total int
items := make([]*v1.RedemptionItem, 0)
err := query.OrderDesc("created_at").
Page(page, pageSize).
ScanAndCount(&items, &total, false)
if err != nil {
return nil, err
}
return &v1.RedemptionListRes{
List: items,
Total: total,
Page: page,
PageSize: pageSize,
}, nil
}
// BatchCreateRedemptions 批量生成兑换码
func (s *sAdmin) BatchCreateRedemptions(ctx context.Context, req *v1.RedemptionCreateReq) (*v1.RedemptionCreateRes, error) {
count := req.Count
if count <= 0 || count > 1000 {
return nil, common.NewBadRequestError("count must be between 1 and 1000")
}
codeType := req.Type
if codeType != "quota" && codeType != "plan" && codeType != "duration" {
return nil, common.NewBadRequestError("type must be quota, plan, or duration")
}
batchNo := fmt.Sprintf("BATCH%s%04d", gtime.Now().Format("YmdHis"), gtime.Now().UnixNano()%10000)
created := 0
for i := 0; i < count; i++ {
code, err := generateCode(12)
if err != nil {
return &v1.RedemptionCreateRes{Created: created}, nil
}
_, err = dao.OrdRedemptions.Ctx(ctx).Insert(do.OrdRedemptions{
Code: code,
Type: codeType,
Value: req.Value,
PlanId: req.PlanID,
DurationDays: req.DurationDays,
MaxUses: 1,
BatchNo: batchNo,
Status: "active",
ExpiresAt: gtime.Now().Add(90 * 24 * time.Hour),
})
if err != nil {
return &v1.RedemptionCreateRes{Created: created}, nil
}
created++
}
return &v1.RedemptionCreateRes{Created: created}, nil
}
// DisableRedemption 禁用兑换码
func (s *sAdmin) DisableRedemption(ctx context.Context, req *v1.RedemptionDisableReq) (*v1.RedemptionDisableRes, error) {
_, err := dao.OrdRedemptions.Ctx(ctx).
Where("id", req.Id).
Where("status", "active").
Data(do.OrdRedemptions{
Status: "disabled",
}).Update()
if err != nil {
return nil, err
}
return &v1.RedemptionDisableRes{}, nil
}
func generateCode(length int) (string, error) {
const charset = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
result := make([]byte, length)
for i := range result {
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
if err != nil {
return "", err
}
result[i] = charset[n.Int64()]
}
return string(result), nil
}
// ListRedemptionUsages 获取兑换码使用记录
func (s *sAdmin) ListRedemptionUsages(ctx context.Context, req *v1.RedemptionUsagesReq) (*v1.RedemptionUsagesRes, error) {
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
var conditions []string
var args []any
if req.RedemptionId > 0 {
conditions = append(conditions, "ru.redemption_id = ?")
args = append(args, req.RedemptionId)
}
if req.TenantId > 0 {
conditions = append(conditions, "ru.tenant_id = ?")
args = append(args, req.TenantId)
}
where := ""
if len(conditions) > 0 {
where = " WHERE " + strings.Join(conditions, " AND ")
}
fromClause := "ord_redemption_usages ru LEFT JOIN ord_redemptions r ON ru.redemption_id = r.id LEFT JOIN tnt_tenants t ON ru.tenant_id = t.id LEFT JOIN tnt_users u ON ru.user_id = u.id AND ru.tenant_id = u.tenant_id"
countSQL := "SELECT COUNT(*) AS total FROM " + fromClause + where
countResult, err := g.DB().Ctx(ctx).Query(ctx, countSQL, args...)
if err != nil {
return nil, err
}
total := 0
if len(countResult) > 0 {
total = countResult[0]["total"].Int()
}
dataSQL := fmt.Sprintf(
`SELECT ru.id, ru.redemption_id, ru.tenant_id, ru.user_id, ru.type, ru.value, ru.transaction_id, ru.created_at,
COALESCE(r.code, '') AS code,
COALESCE(t.name, '') AS tenant_name,
COALESCE(u.username, '') AS username
FROM %s%s ORDER BY ru.created_at DESC LIMIT %d OFFSET %d`,
fromClause, where, pageSize, (page-1)*pageSize,
)
result, err := g.DB().Ctx(ctx).Query(ctx, dataSQL, args...)
if err != nil {
return nil, err
}
list := make([]*v1.RedemptionUsageItem, 0, len(result))
for _, row := range result {
list = append(list, &v1.RedemptionUsageItem{
Id: row["id"].Int64(),
RedemptionId: row["redemption_id"].Int64(),
Code: row["code"].String(),
TenantId: row["tenant_id"].Int64(),
TenantName: row["tenant_name"].String(),
UserId: row["user_id"].Int64(),
Username: row["username"].String(),
Type: row["type"].String(),
Value: row["value"].Float64(),
TransactionId: row["transaction_id"].Int64(),
CreatedAt: gtime.NewFromTime(row["created_at"].Time()),
})
}
return &v1.RedemptionUsagesRes{
List: list,
Total: total,
Page: page,
PageSize: pageSize,
}, nil
}
// ExportRedemptions exports redemption list to CSV or Excel.
func (s *sAdmin) ExportRedemptions(ctx context.Context, req *v1.RedemptionExportReq) (*v1.RedemptionExportRes, error) {
columns := []export.Column{
{Field: "id", Header: "ID"},
{Field: "code", Header: "兑换码"},
{Field: "type", Header: "类型"},
{Field: "value", Header: "面值"},
{Field: "used_count", Header: "已用次数"},
{Field: "status", Header: "状态"},
{Field: "batch_no", Header: "批次号"},
{Field: "created_at", Header: "创建时间"},
}
config := export.Config{
Format: req.Format,
Filename: "兑换码_" + gtime.Now().Format("Ymd_His"),
Columns: columns,
}
redemptionFields := "id, code, type, value, used_count, status, batch_no, created_at"
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
offset := 0
for {
query := dao.OrdRedemptions.Ctx(ctx)
if req.Status != "" {
query = query.Where("status", req.Status)
}
var batch []struct {
Id int64 `json:"id"`
Code string `json:"code"`
Type string `json:"type"`
Value float64 `json:"value"`
UsedCount int `json:"used_count"`
Status string `json:"status"`
BatchNo string `json:"batch_no"`
CreatedAt *gtime.Time `json:"created_at"`
}
if err := query.Fields(redemptionFields).OrderDesc("created_at").Limit(1000).Offset(offset).Scan(&batch); err != nil {
return
}
for _, item := range batch {
if !yield(map[string]any{
"id": item.Id,
"code": item.Code,
"type": item.Type,
"value": item.Value,
"used_count": item.UsedCount,
"status": item.Status,
"batch_no": item.BatchNo,
"created_at": item.CreatedAt.String(),
}) {
return
}
}
if len(batch) < 1000 {
break
}
offset += 1000
}
})
}