Some checks failed
Test / test (push) Failing after 3m47s
- 替换 Field(max_length=X) 为 StrX/TextX 类型别名(21 个 sqlmodels 文件) - 替换 get + 404 检查为 get_exist_one()(17 个路由文件,约 50 处) - 替换 save + session.refresh 为 save(load=...) - 替换 session.add + commit 为 save()(dav/provider.py) - 更新所有依赖至最新版本 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
207 lines
4.9 KiB
Python
207 lines
4.9 KiB
Python
from decimal import Decimal
|
||
from enum import StrEnum
|
||
from typing import TYPE_CHECKING
|
||
from uuid import UUID
|
||
|
||
from sqlalchemy import Numeric, BigInteger
|
||
from sqlmodel import Field, Relationship, text
|
||
|
||
from sqlmodel_ext import SQLModelBase, UUIDTableBaseMixin, Str255
|
||
|
||
if TYPE_CHECKING:
|
||
from .order import Order
|
||
from .redeem import Redeem
|
||
|
||
|
||
class ProductType(StrEnum):
|
||
"""商品类型枚举"""
|
||
|
||
STORAGE_PACK = "storage_pack"
|
||
"""容量包"""
|
||
|
||
GROUP_TIME = "group_time"
|
||
"""用户组时长"""
|
||
|
||
SCORE = "score"
|
||
"""积分充值"""
|
||
|
||
|
||
class PaymentMethod(StrEnum):
|
||
"""支付方式枚举"""
|
||
|
||
ALIPAY = "alipay"
|
||
"""支付宝"""
|
||
|
||
WECHAT = "wechat"
|
||
"""微信支付"""
|
||
|
||
STRIPE = "stripe"
|
||
"""Stripe"""
|
||
|
||
EASYPAY = "easypay"
|
||
"""易支付"""
|
||
|
||
CUSTOM = "custom"
|
||
"""自定义支付"""
|
||
|
||
|
||
# ==================== DTO 模型 ====================
|
||
|
||
class ProductBase(SQLModelBase):
|
||
"""商品基础字段"""
|
||
|
||
name: str
|
||
"""商品名称"""
|
||
|
||
type: ProductType
|
||
"""商品类型"""
|
||
|
||
description: str | None = None
|
||
"""商品描述"""
|
||
|
||
|
||
class ProductCreateRequest(ProductBase):
|
||
"""创建商品请求 DTO"""
|
||
|
||
name: Str255
|
||
"""商品名称"""
|
||
|
||
price: Decimal = Field(ge=0, decimal_places=2)
|
||
"""商品价格(元)"""
|
||
|
||
is_active: bool = True
|
||
"""是否上架"""
|
||
|
||
sort_order: int = Field(default=0, ge=0)
|
||
"""排序权重(越大越靠前)"""
|
||
|
||
# storage_pack 专用
|
||
size: int | None = Field(default=None, ge=0)
|
||
"""容量大小(字节),type=storage_pack 时必填"""
|
||
|
||
duration_days: int | None = Field(default=None, ge=1)
|
||
"""有效天数,type=storage_pack/group_time 时必填"""
|
||
|
||
# group_time 专用
|
||
group_id: UUID | None = None
|
||
"""目标用户组UUID,type=group_time 时必填"""
|
||
|
||
# score 专用
|
||
score_amount: int | None = Field(default=None, ge=1)
|
||
"""积分数量,type=score 时必填"""
|
||
|
||
|
||
class ProductUpdateRequest(SQLModelBase):
|
||
"""更新商品请求 DTO(所有字段可选)"""
|
||
|
||
name: Str255 | None = None
|
||
"""商品名称"""
|
||
|
||
description: str | None = None
|
||
"""商品描述"""
|
||
|
||
price: Decimal | None = Field(default=None, ge=0, decimal_places=2)
|
||
"""商品价格(元)"""
|
||
|
||
is_active: bool | None = None
|
||
"""是否上架"""
|
||
|
||
sort_order: int | None = Field(default=None, ge=0)
|
||
"""排序权重"""
|
||
|
||
size: int | None = Field(default=None, ge=0)
|
||
"""容量大小(字节)"""
|
||
|
||
duration_days: int | None = Field(default=None, ge=1)
|
||
"""有效天数"""
|
||
|
||
group_id: UUID | None = None
|
||
"""目标用户组UUID"""
|
||
|
||
score_amount: int | None = Field(default=None, ge=1)
|
||
"""积分数量"""
|
||
|
||
|
||
class ProductResponse(ProductBase):
|
||
"""商品响应 DTO"""
|
||
|
||
id: UUID
|
||
"""商品UUID"""
|
||
|
||
price: float
|
||
"""商品价格(元)"""
|
||
|
||
is_active: bool
|
||
"""是否上架"""
|
||
|
||
sort_order: int
|
||
"""排序权重"""
|
||
|
||
size: int | None = None
|
||
"""容量大小(字节)"""
|
||
|
||
duration_days: int | None = None
|
||
"""有效天数"""
|
||
|
||
group_id: UUID | None = None
|
||
"""目标用户组UUID"""
|
||
|
||
score_amount: int | None = None
|
||
"""积分数量"""
|
||
|
||
|
||
# ==================== 数据库模型 ====================
|
||
|
||
class Product(ProductBase, UUIDTableBaseMixin):
|
||
"""商品模型"""
|
||
|
||
name: Str255
|
||
"""商品名称"""
|
||
|
||
price: Decimal = Field(sa_type=Numeric(12, 2), default=Decimal("0.00"))
|
||
"""商品价格(元)"""
|
||
|
||
is_active: bool = Field(default=True, sa_column_kwargs={"server_default": text("true")})
|
||
"""是否上架"""
|
||
|
||
sort_order: int = Field(default=0, sa_column_kwargs={"server_default": "0"})
|
||
"""排序权重(越大越靠前)"""
|
||
|
||
# storage_pack 专用
|
||
size: int | None = Field(default=None, sa_type=BigInteger)
|
||
"""容量大小(字节),type=storage_pack 时必填"""
|
||
|
||
duration_days: int | None = None
|
||
"""有效天数,type=storage_pack/group_time 时必填"""
|
||
|
||
# group_time 专用
|
||
group_id: UUID | None = Field(default=None, foreign_key="group.id", ondelete="SET NULL")
|
||
"""目标用户组UUID,type=group_time 时必填"""
|
||
|
||
# score 专用
|
||
score_amount: int | None = None
|
||
"""积分数量,type=score 时必填"""
|
||
|
||
# 关系
|
||
orders: list["Order"] = Relationship(back_populates="product")
|
||
"""关联的订单列表"""
|
||
|
||
redeems: list["Redeem"] = Relationship(back_populates="product")
|
||
"""关联的兑换码列表"""
|
||
|
||
def to_response(self) -> ProductResponse:
|
||
"""转换为响应 DTO"""
|
||
return ProductResponse(
|
||
id=self.id,
|
||
name=self.name,
|
||
type=self.type,
|
||
description=self.description,
|
||
price=float(self.price),
|
||
is_active=self.is_active,
|
||
sort_order=self.sort_order,
|
||
size=self.size,
|
||
duration_days=self.duration_days,
|
||
group_id=self.group_id,
|
||
score_amount=self.score_amount,
|
||
)
|