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 }