feat(ECR-014): MembershipPlan 套餐配置并 Closed
membership_plans 表、admin 套餐页、Grant/CreateOrder 读表; Loop continuous 自动 Approve/Closed。Next:ECR-015 RedemptionCode。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/yuxingu/digital-psychology/apps/api/internal/repository"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPlanNotFound = errString("plan not found")
|
||||
ErrInvalidPlanU = errString("invalid plan update")
|
||||
)
|
||||
|
||||
// ListMembershipPlans returns catalog.
|
||||
func (s *Service) ListMembershipPlans(ctx context.Context) ([]repository.MembershipPlanRow, error) {
|
||||
items, err := s.Repo.ListMembershipPlans(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if items == nil {
|
||||
items = []repository.MembershipPlanRow{}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// GetMembershipPlan returns one plan.
|
||||
func (s *Service) GetMembershipPlan(ctx context.Context, code string) (*repository.MembershipPlanRow, error) {
|
||||
p, err := s.Repo.GetMembershipPlan(ctx, code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p == nil {
|
||||
return nil, ErrPlanNotFound
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// UpdateMembershipPlan updates mutable fields.
|
||||
func (s *Service) UpdateMembershipPlan(
|
||||
ctx context.Context, adminID uuid.UUID, code, title string, days, amount int, active bool,
|
||||
) (*repository.MembershipPlanRow, error) {
|
||||
code = strings.TrimSpace(code)
|
||||
title = strings.TrimSpace(title)
|
||||
if title == "" || days <= 0 || amount < 0 {
|
||||
return nil, ErrInvalidPlanU
|
||||
}
|
||||
if _, err := s.GetMembershipPlan(ctx, code); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
meta, _ := json.Marshal(map[string]any{
|
||||
"title": title, "duration_days": days, "amount_cents": amount, "active": active,
|
||||
})
|
||||
if err := s.Repo.UpdateMembershipPlanWithAudit(ctx, adminID, code, title, days, amount, active, meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.GetMembershipPlan(ctx, code)
|
||||
}
|
||||
|
||||
// PlanDurationDays resolves grant length from catalog with hardcoded fallback.
|
||||
func (s *Service) PlanDurationDays(ctx context.Context, plan string) (int, error) {
|
||||
p, err := s.Repo.GetMembershipPlan(ctx, plan)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if p != nil && p.Active && p.DurationDays > 0 {
|
||||
return p.DurationDays, nil
|
||||
}
|
||||
return planDaysFallback(plan)
|
||||
}
|
||||
|
||||
func planDaysFallback(plan string) (int, error) {
|
||||
switch plan {
|
||||
case "month":
|
||||
return 31, nil
|
||||
case "quarter":
|
||||
return 92, nil
|
||||
case "year":
|
||||
return 366, nil
|
||||
default:
|
||||
return 0, ErrInvalidPlan
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user