95 lines
4.7 KiB
Python
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)
|