fix(admin): restore currency and timestamps in admin plan list response

The composite-groups PR (#3581) replaced the raw ent SubscriptionPlan
response of GET /admin/payment/plans with a projection struct but
dropped the currency field added by #4323. PlanEditDialog then read an
undefined currency, sent an empty string on save, and silently wiped
the stored plan currency. Restore currency plus created_at/updated_at
so the projection preserves the full original response shape.
This commit is contained in:
shaw
2026-07-23 10:26:46 +08:00
parent 3e5d4af411
commit 90c4f50a5e
2 changed files with 37 additions and 19 deletions
@@ -288,25 +288,28 @@ func (h *PaymentHandler) ListPlans(c *gin.Context) {
}
type AdminSubscriptionPlanResult struct {
ID int64 `json:"id"`
GroupID int64 `json:"group_id"`
GroupPlatform string `json:"group_platform,omitempty"`
GroupName string `json:"group_name,omitempty"`
RateMultiplier float64 `json:"rate_multiplier,omitempty"`
DailyLimitUSD *float64 `json:"daily_limit_usd,omitempty"`
WeeklyLimitUSD *float64 `json:"weekly_limit_usd,omitempty"`
MonthlyLimitUSD *float64 `json:"monthly_limit_usd,omitempty"`
ModelScopes []string `json:"supported_model_scopes,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
Price float64 `json:"price"`
OriginalPrice *float64 `json:"original_price,omitempty"`
ValidityDays int `json:"validity_days"`
ValidityUnit string `json:"validity_unit"`
Features string `json:"features"`
ProductName string `json:"product_name"`
ForSale bool `json:"for_sale"`
SortOrder int `json:"sort_order"`
ID int64 `json:"id"`
GroupID int64 `json:"group_id"`
GroupPlatform string `json:"group_platform,omitempty"`
GroupName string `json:"group_name,omitempty"`
RateMultiplier float64 `json:"rate_multiplier,omitempty"`
DailyLimitUSD *float64 `json:"daily_limit_usd,omitempty"`
WeeklyLimitUSD *float64 `json:"weekly_limit_usd,omitempty"`
MonthlyLimitUSD *float64 `json:"monthly_limit_usd,omitempty"`
ModelScopes []string `json:"supported_model_scopes,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
Price float64 `json:"price"`
OriginalPrice *float64 `json:"original_price,omitempty"`
Currency string `json:"currency,omitempty"`
ValidityDays int `json:"validity_days"`
ValidityUnit string `json:"validity_unit"`
Features string `json:"features"`
ProductName string `json:"product_name"`
ForSale bool `json:"for_sale"`
SortOrder int `json:"sort_order"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
func adminSubscriptionPlansForResponse(plans []*dbent.SubscriptionPlan, groupInfo map[int64]service.PlanGroupInfo) []AdminSubscriptionPlanResult {
@@ -330,12 +333,15 @@ func adminSubscriptionPlansForResponse(plans []*dbent.SubscriptionPlan, groupInf
Description: p.Description,
Price: p.Price,
OriginalPrice: p.OriginalPrice,
Currency: p.Currency,
ValidityDays: p.ValidityDays,
ValidityUnit: p.ValidityUnit,
Features: p.Features,
ProductName: p.ProductName,
ForSale: p.ForSale,
SortOrder: p.SortOrder,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
})
}
return result
@@ -50,6 +50,7 @@ func TestSanitizeAdminPaymentOrderForResponseAddsCurrency(t *testing.T) {
func TestAdminSubscriptionPlansForResponseIncludesCompositeGroupInfo(t *testing.T) {
weekly := 25.0
now := time.Now()
plans := []*dbent.SubscriptionPlan{
{
ID: 11,
@@ -57,12 +58,15 @@ func TestAdminSubscriptionPlansForResponseIncludesCompositeGroupInfo(t *testing.
Name: "All models",
Description: "Composite access",
Price: 19.99,
Currency: "CNY",
ValidityDays: 30,
ValidityUnit: "days",
Features: "OpenAI\nClaude\nGemini\nGrok",
ProductName: "Sub2API",
ForSale: true,
SortOrder: 1,
CreatedAt: now,
UpdatedAt: now,
},
}
groupInfo := map[int64]service.PlanGroupInfo{
@@ -92,4 +96,12 @@ func TestAdminSubscriptionPlansForResponseIncludesCompositeGroupInfo(t *testing.
if strings.Join(got[0].ModelScopes, ",") != "openai,claude,gemini,grok" {
t.Fatalf("expected model scopes to be preserved, got %#v", got[0].ModelScopes)
}
// 投影必须保留 ent 原始响应的全部套餐字段:currency 丢失曾导致编辑保存时
// 静默清空套餐货币(PlanEditDialog 回传空串 → SetCurrency(""))。
if got[0].Currency != "CNY" {
t.Fatalf("expected currency to be preserved, got %q", got[0].Currency)
}
if !got[0].CreatedAt.Equal(now) || !got[0].UpdatedAt.Equal(now) {
t.Fatalf("expected created_at/updated_at to be preserved, got %v / %v", got[0].CreatedAt, got[0].UpdatedAt)
}
}