feat(redeem): 兑换码支持设置使用有效期

This commit is contained in:
wucm667 committed 2026-05-19 15:53:28 +08:00
1 parent b006e36af9
commit e4aaf0af29
27 files changed
+676 -38

No files matched your search

@@ -30,6 +30,7 @@ func (r *redeemCodeRepository) Create(ctx context.Context, code *service.RedeemC
SetStatus(code.Status).
SetNotes(code.Notes).
SetValidityDays(code.ValidityDays).
SetNillableExpiresAt(code.ExpiresAt).
SetNillableUsedBy(code.UsedBy).
SetNillableUsedAt(code.UsedAt).
SetNillableGroupID(code.GroupID).
@@ -56,6 +57,7 @@ func (r *redeemCodeRepository) CreateBatch(ctx context.Context, codes []service.
SetStatus(c.Status).
SetNotes(c.Notes).
SetValidityDays(c.ValidityDays).
SetNillableExpiresAt(c.ExpiresAt).
SetNillableUsedBy(c.UsedBy).
SetNillableUsedAt(c.UsedAt).
SetNillableGroupID(c.GroupID)
@@ -107,7 +109,28 @@ func (r *redeemCodeRepository) ListWithFilters(ctx context.Context, params pagin
q = q.Where(redeemcode.TypeEQ(codeType))
}
if status != "" {
q = q.Where(redeemcode.StatusEQ(status))
now := time.Now()
switch status {
case service.StatusExpired:
q = q.Where(redeemcode.Or(
redeemcode.StatusEQ(service.StatusExpired),
redeemcode.And(
redeemcode.StatusEQ(service.StatusUnused),
redeemcode.ExpiresAtNotNil(),
redeemcode.ExpiresAtLTE(now),
),
))
case service.StatusUnused:
q = q.Where(
redeemcode.StatusEQ(service.StatusUnused),
redeemcode.Or(
redeemcode.ExpiresAtIsNil(),
redeemcode.ExpiresAtGT(now),
),
)
default:
q = q.Where(redeemcode.StatusEQ(status))
}
}
if search != "" {
q = q.Where(
@@ -158,6 +181,8 @@ func redeemCodeListOrder(params pagination.PaginationParams) []func(*entsql.Sele
field = redeemcode.FieldUsedAt
case "created_at":
field = redeemcode.FieldCreatedAt
case "expires_at":
field = redeemcode.FieldExpiresAt
case "code":
field = redeemcode.FieldCode
default:
@@ -194,6 +219,11 @@ func (r *redeemCodeRepository) Update(ctx context.Context, code *service.RedeemC
} else {
up.ClearGroupID()
}
if code.ExpiresAt != nil {
up.SetExpiresAt(*code.ExpiresAt)
} else {
up.ClearExpiresAt()
}
updated, err := up.Save(ctx)
if err != nil {
@@ -307,6 +337,7 @@ func redeemCodeEntityToService(m *dbent.RedeemCode) *service.RedeemCode {
UsedAt: m.UsedAt,
Notes: derefString(m.Notes),
CreatedAt: m.CreatedAt,
ExpiresAt: m.ExpiresAt,
GroupID: m.GroupID,
ValidityDays: m.ValidityDays,
}
@@ -51,11 +51,13 @@ func (s *RedeemCodeRepoSuite) createGroup(name string) *dbent.Group {
// --- Create / CreateBatch / GetByID / GetByCode ---
func (s *RedeemCodeRepoSuite) TestCreate() {
expiresAt := time.Now().UTC().Add(2 * time.Hour)
code := &service.RedeemCode{
Code: "TEST-CREATE",
Type: service.RedeemTypeBalance,
Value: 100,
Status: service.StatusUnused,
Code: "TEST-CREATE",
Type: service.RedeemTypeBalance,
Value: 100,
Status: service.StatusUnused,
ExpiresAt: &expiresAt,
}
err := s.repo.Create(s.ctx, code)
@@ -65,6 +67,8 @@ func (s *RedeemCodeRepoSuite) TestCreate() {
got, err := s.repo.GetByID(s.ctx, code.ID)
s.Require().NoError(err, "GetByID")
s.Require().Equal("TEST-CREATE", got.Code)
s.Require().NotNil(got.ExpiresAt)
s.Require().WithinDuration(expiresAt, *got.ExpiresAt, time.Second)
}
func (s *RedeemCodeRepoSuite) TestCreateBatch() {
@@ -166,6 +170,23 @@ func (s *RedeemCodeRepoSuite) TestListWithFilters_Status() {
s.Require().Equal(service.StatusUsed, codes[0].Status)
}
func (s *RedeemCodeRepoSuite) TestListWithFilters_StatusExpiredByExpiresAt() {
past := time.Now().UTC().Add(-time.Hour)
future := time.Now().UTC().Add(time.Hour)
s.Require().NoError(s.repo.Create(s.ctx, &service.RedeemCode{Code: "STAT-EXPIRED-BY-TIME", Type: service.RedeemTypeBalance, Value: 0, Status: service.StatusUnused, ExpiresAt: &past}))
s.Require().NoError(s.repo.Create(s.ctx, &service.RedeemCode{Code: "STAT-UNUSED-FUTURE", Type: service.RedeemTypeBalance, Value: 0, Status: service.StatusUnused, ExpiresAt: &future}))
expired, _, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{Page: 1, PageSize: 10}, "", service.StatusExpired, "")
s.Require().NoError(err)
s.Require().Len(expired, 1)
s.Require().Equal("STAT-EXPIRED-BY-TIME", expired[0].Code)
unused, _, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{Page: 1, PageSize: 10}, "", service.StatusUnused, "")
s.Require().NoError(err)
s.Require().Len(unused, 1)
s.Require().Equal("STAT-UNUSED-FUTURE", unused[0].Code)
}
func (s *RedeemCodeRepoSuite) TestListWithFilters_Search() {
s.Require().NoError(s.repo.Create(s.ctx, &service.RedeemCode{Code: "ALPHA-CODE", Type: service.RedeemTypeBalance, Value: 0, Status: service.StatusUnused}))
s.Require().NoError(s.repo.Create(s.ctx, &service.RedeemCode{Code: "BETA-CODE", Type: service.RedeemTypeBalance, Value: 0, Status: service.StatusUnused}))