feat(ECR-015): RedemptionCode 兑换码并 Closed
批次生成/作废、C 端兑码延长会员;admin-h5 /codes。 Loop continuous。Next:ECR-016 UserIntelligence。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -22,6 +22,8 @@ const (
|
||||
PermUsersStatusWrite = "admin.users.status.write"
|
||||
PermMembershipPlansRead = "admin.membership.plans.read"
|
||||
PermMembershipPlansWrite = "admin.membership.plans.write"
|
||||
PermMembershipCodesRead = "admin.membership.codes.read"
|
||||
PermMembershipCodesWrite = "admin.membership.codes.write"
|
||||
)
|
||||
|
||||
var knownPermissions = map[string]struct{}{
|
||||
@@ -29,6 +31,7 @@ var knownPermissions = map[string]struct{}{
|
||||
PermOrdersRead: {}, PermAuditRead: {}, PermAnalyticsRead: {},
|
||||
PermContentWrite: {}, PermRolesRead: {}, PermRolesWrite: {},
|
||||
PermUsersStatusWrite: {}, PermMembershipPlansRead: {}, PermMembershipPlansWrite: {},
|
||||
PermMembershipCodesRead: {}, PermMembershipCodesWrite: {},
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/yuxingu/digital-psychology/apps/api/internal/repository"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBadBatchQty = errString("quantity must be 1..100")
|
||||
ErrCodeDisable = errString("code not unused")
|
||||
ErrBatchNotFound = errString("batch not found")
|
||||
)
|
||||
|
||||
// CreateRedemptionBatch generates codes for a membership plan.
|
||||
func (s *Service) CreateRedemptionBatch(
|
||||
ctx context.Context, adminID uuid.UUID, label, planCode string, qty int,
|
||||
) (*repository.RedemptionBatch, []repository.RedemptionCodeRow, error) {
|
||||
label = strings.TrimSpace(label)
|
||||
planCode = strings.TrimSpace(planCode)
|
||||
if label == "" || qty < 1 || qty > 100 {
|
||||
return nil, nil, ErrBadBatchQty
|
||||
}
|
||||
if _, err := s.GetMembershipPlan(ctx, planCode); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
codes := make([]string, 0, qty)
|
||||
seen := map[string]struct{}{}
|
||||
for len(codes) < qty {
|
||||
c, err := newRedemptionCode()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if _, ok := seen[c]; ok {
|
||||
continue
|
||||
}
|
||||
seen[c] = struct{}{}
|
||||
codes = append(codes, c)
|
||||
}
|
||||
meta, _ := json.Marshal(map[string]any{"label": label, "plan": planCode, "quantity": qty})
|
||||
return s.Repo.CreateRedemptionBatchWithCodes(ctx, adminID, label, planCode, codes, meta)
|
||||
}
|
||||
|
||||
// ListRedemptionBatches lists recent batches.
|
||||
func (s *Service) ListRedemptionBatches(ctx context.Context, limit int) ([]repository.RedemptionBatch, error) {
|
||||
items, err := s.Repo.ListRedemptionBatches(ctx, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if items == nil {
|
||||
items = []repository.RedemptionBatch{}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// ListRedemptionCodes lists codes in a batch.
|
||||
func (s *Service) ListRedemptionCodes(ctx context.Context, batchID uuid.UUID) ([]repository.RedemptionCodeRow, error) {
|
||||
ok, err := s.Repo.BatchExists(ctx, batchID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, ErrBatchNotFound
|
||||
}
|
||||
items, err := s.Repo.ListRedemptionCodesByBatch(ctx, batchID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if items == nil {
|
||||
items = []repository.RedemptionCodeRow{}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// DisableRedemptionCode voids an unused code.
|
||||
func (s *Service) DisableRedemptionCode(ctx context.Context, adminID, codeID uuid.UUID) error {
|
||||
err := s.Repo.DisableRedemptionCode(ctx, adminID, codeID)
|
||||
if err != nil && err.Error() == "code not unused" {
|
||||
return ErrCodeDisable
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func newRedemptionCode() (string, error) {
|
||||
b := make([]byte, 6)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "YXG-" + strings.ToUpper(hex.EncodeToString(b)), nil
|
||||
}
|
||||
Reference in New Issue
Block a user