Files
video-gen/video-gen-api/app/models/credit/balance.py
T
2026-08-11 09:24:18 +08:00

95 lines
4.7 KiB
Python

from __future__ import annotations
from datetime import datetime
from decimal import Decimal
from sqlalchemy import CheckConstraint, DateTime, ForeignKey, Index, JSON, Numeric, String, text
from sqlalchemy.orm import Mapped, mapped_column
from app.enums.credit_balance import CreditBalanceStatus
from app.models.base import Base, TimestampMixin
class UserCreditBalance(Base, TimestampMixin):
__tablename__ = "user_credit_balances"
__table_args__ = (
CheckConstraint("grant_amount >= 0", name="ck_user_credit_balances_grant_nonnegative"),
CheckConstraint("unspent_amount >= 0", name="ck_user_credit_balances_unspent_nonnegative"),
CheckConstraint("consumed_amount >= 0", name="ck_user_credit_balances_consumed_nonnegative"),
CheckConstraint("expired_amount >= 0", name="ck_user_credit_balances_expired_nonnegative"),
CheckConstraint("revoked_amount >= 0", name="ck_user_credit_balances_revoked_nonnegative"),
CheckConstraint(
"grant_amount = unspent_amount + consumed_amount + expired_amount + revoked_amount",
name="ck_user_credit_balances_amount_reconciled",
),
CheckConstraint("expires_at > valid_from", name="ck_user_credit_balances_valid_window"),
Index(
"ix_user_credit_balances_spendable",
"user_id",
"credit_level_rank",
"expires_at",
"valid_from",
"id",
postgresql_where=text("unspent_amount > 0 AND revoked_at IS NULL"),
),
Index(
"ix_user_credit_balances_expire_due",
"expires_at",
"id",
postgresql_where=text(
"unspent_amount > 0 AND expired_processed_at IS NULL AND revoked_at IS NULL"
),
),
Index("ix_user_credit_balances_source", "source_type", "source_id"),
Index("ix_user_credit_balances_payment", "payment_order_id"),
Index("ix_user_credit_balances_subscription", "subscription_id", "subscription_period_id"),
Index("uq_user_credit_balances_user_biz_key", "user_id", "biz_key", unique=True),
)
id: Mapped[str] = mapped_column(String(32), primary_key=True)
user_id: Mapped[str] = mapped_column(
String(32), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True
)
credit_level: Mapped[str] = mapped_column(String(32), nullable=False)
credit_level_rank: Mapped[int] = mapped_column(nullable=False, default=20, server_default="20")
source_type: Mapped[str] = mapped_column(String(48), nullable=False, index=True)
source_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
product_id: Mapped[str | None] = mapped_column(
String(32), ForeignKey("credit_products.id", ondelete="SET NULL"), nullable=True
)
payment_order_id: Mapped[str | None] = mapped_column(
String(32), ForeignKey("payment_orders.id", ondelete="SET NULL"), nullable=True
)
subscription_id: Mapped[str | None] = mapped_column(
String(32), ForeignKey("user_credit_subscriptions.id", ondelete="SET NULL"), nullable=True
)
subscription_period_id: Mapped[str | None] = mapped_column(
String(32), ForeignKey("user_credit_subscription_periods.id", ondelete="SET NULL"), nullable=True
)
grant_record_id: Mapped[str | None] = mapped_column(
String(32), ForeignKey("credit_records.id", ondelete="SET NULL"), nullable=True, index=True
)
grant_amount: Mapped[Decimal] = mapped_column(Numeric(20, 2), nullable=False)
unspent_amount: Mapped[Decimal] = mapped_column(Numeric(20, 2), nullable=False)
consumed_amount: Mapped[Decimal] = mapped_column(
Numeric(20, 2), nullable=False, default=Decimal("0.00"), server_default="0"
)
expired_amount: Mapped[Decimal] = mapped_column(
Numeric(20, 2), nullable=False, default=Decimal("0.00"), server_default="0"
)
revoked_amount: Mapped[Decimal] = mapped_column(
Numeric(20, 2), nullable=False, default=Decimal("0.00"), server_default="0"
)
valid_from: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True)
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True)
status: Mapped[str] = mapped_column(
String(24), nullable=False, default=CreditBalanceStatus.ACTIVE.value, server_default=CreditBalanceStatus.ACTIVE.value
)
expired_processed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
biz_key: Mapped[str] = mapped_column(String(180), nullable=False)
metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)