Files
video-gen/video-gen-api/app/schemas/credit_product.py
T
2026-08-14 15:23:46 +08:00

166 lines
6.7 KiB
Python

from __future__ import annotations
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, Field, model_validator
ProductType = Literal["subscription", "team_subscription", "credit_addon"]
BillingCycle = Literal["monthly", "quarterly", "yearly"]
CreditLevelType = Literal["promotional", "general"]
class CreditProductBase(BaseModel):
product_code: str = Field(..., min_length=1, max_length=64)
product_type: ProductType
name: str = Field(..., min_length=1, max_length=96)
description: str | None = Field(default=None, max_length=512)
features: list[str] = Field(default_factory=list)
tier_code: str | None = Field(default=None, max_length=32)
tier_rank: int | None = Field(default=None, ge=1, le=999)
billing_cycle: BillingCycle | None = None
monthly_grant_credits: float | None = Field(default=None, gt=0, le=999999999.99)
first_purchase_price: float | None = Field(default=None, ge=0, le=999999999.99)
regular_price: float | None = Field(default=None, ge=0, le=999999999.99)
activity_price: float | None = Field(default=None, ge=0, le=999999999.99)
activity_start_at: datetime | None = None
activity_end_at: datetime | None = None
renewal_enabled: bool = True
price: float = Field(default=0, ge=0, le=999999999.99)
grant_credits: float | None = Field(default=None, gt=0, le=999999999.99)
validity_months: int | None = Field(default=None, ge=1, le=36)
credit_level: CreditLevelType = "general"
currency: str = Field(default="CNY", min_length=1, max_length=8)
is_active: bool = True
sort_order: int = Field(default=0, ge=-999999, le=999999)
@model_validator(mode="after")
def validate_product_fields(self):
if self.product_type in {"subscription", "team_subscription"}:
required = {
"tier_code": self.tier_code,
"tier_rank": self.tier_rank,
"billing_cycle": self.billing_cycle,
"monthly_grant_credits": self.monthly_grant_credits,
"first_purchase_price": self.first_purchase_price,
"regular_price": self.regular_price,
}
missing = [name for name, value in required.items() if value is None]
if missing:
raise ValueError(f"订阅套餐缺少字段: {', '.join(missing)}")
if self.grant_credits is not None or self.validity_months is not None:
raise ValueError("订阅套餐不能配置积分增值包字段")
if self.activity_price is not None:
if not self.activity_start_at or not self.activity_end_at:
raise ValueError("配置活动价时必须同时配置活动开始和结束时间")
if self.activity_end_at <= self.activity_start_at:
raise ValueError("活动结束时间必须晚于开始时间")
else:
if self.grant_credits is None:
raise ValueError("积分增值包必须配置积分数量")
if self.validity_months is None:
self.validity_months = 1
if any(
value is not None
for value in (
self.tier_code,
self.tier_rank,
self.billing_cycle,
self.monthly_grant_credits,
self.first_purchase_price,
self.regular_price,
self.activity_price,
self.activity_start_at,
self.activity_end_at,
)
):
raise ValueError("积分增值包不能配置订阅套餐字段")
return self
class CreditProductCreate(CreditProductBase):
pass
class CreditProductUpdate(BaseModel):
# product_code / product_type / tier_code / tier_rank / billing_cycle 创建后永久不可修改。
name: str | None = Field(default=None, min_length=1, max_length=96)
description: str | None = Field(default=None, max_length=512)
features: list[str] | None = None
monthly_grant_credits: float | None = Field(default=None, gt=0, le=999999999.99)
first_purchase_price: float | None = Field(default=None, ge=0, le=999999999.99)
regular_price: float | None = Field(default=None, ge=0, le=999999999.99)
activity_price: float | None = Field(default=None, ge=0, le=999999999.99)
activity_start_at: datetime | None = None
activity_end_at: datetime | None = None
renewal_enabled: bool | None = None
price: float | None = Field(default=None, ge=0, le=999999999.99)
grant_credits: float | None = Field(default=None, gt=0, le=999999999.99)
validity_months: int | None = Field(default=None, ge=1, le=36)
credit_level: CreditLevelType | None = None
currency: str | None = Field(default=None, min_length=1, max_length=8)
sort_order: int | None = Field(default=None, ge=-999999, le=999999)
class CreditProductRenewalUpdate(BaseModel):
renewal_enabled: bool
class CreditProductStatusUpdate(BaseModel):
is_active: bool
class CreditProductOut(BaseModel):
id: str
product_code: str
product_type: str
product_type_label: str = ""
name: str
description: str | None = None
features: list[str] = Field(default_factory=list)
tier_code: str | None = None
tier_label: str | None = None
tier_rank: int | None = None
billing_cycle: str | None = None
billing_cycle_label: str | None = None
monthly_grant_credits: float = 0
grant_count: int = 1
first_purchase_price: float = 0
regular_price: float = 0
activity_price: float | None = None
activity_start_at: datetime | None = None
activity_end_at: datetime | None = None
renewal_enabled: bool = True
grant_credits: float = 0
validity_months: int | None = None
price: float
current_price: float
price_type: str | None = None
price_type_label: str | None = None
credit_level: str
credit_level_label: str = ""
currency: str
is_active: bool
is_deleted: bool = False
deleted_at: datetime | None = None
status_label: str = ""
sort_order: int
can_purchase: bool | None = None
unavailable_reason: str | None = None
class CreditProductCatalogOut(BaseModel):
subscription_products: list[CreditProductOut]
team_subscription_products: list[CreditProductOut]
credit_addons: list[CreditProductOut]
personal_first_purchase_available: bool
team_first_purchase_available: bool
active_personal_subscriptions: list[dict] = Field(default_factory=list)
personal_entitlement: dict | None = None
team_entitlement: dict | None = None
team_purchase_available: bool = False
team_purchase_unavailable_reason: str | None = None