from datetime import datetime from enum import Enum from typing import TYPE_CHECKING, Optional from sqlalchemy import BigInteger, DateTime, ForeignKey, Index, Integer, String, func from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database.session import Base if TYPE_CHECKING: from app.backoffice.admin.models import Admin from app.user.models import User class ChargeRequestStatus(str, Enum): PENDING = "pending" APPROVED = "approved" REJECTED = "rejected" CANCELLED = "cancelled" class CreditTransactionType(str, Enum): CHARGE = "charge" CONSUME = "consume" REFUND = "refund" ADMIN_ADJUST = "admin_adjust" class CreditChargeRequest(Base): __tablename__ = "credit_charge_request" __table_args__ = ( Index("idx_credit_request_user_uuid", "user_uuid"), Index("idx_credit_request_status", "status"), Index("idx_credit_request_created_at", "created_at"), Index("idx_credit_request_status_created", "status", "created_at"), { "mysql_engine": "InnoDB", "mysql_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci", }, ) id: Mapped[int] = mapped_column( BigInteger, primary_key=True, nullable=False, autoincrement=True, comment="고유 식별자", ) user_uuid: Mapped[str] = mapped_column( String(36), ForeignKey("user.user_uuid", ondelete="CASCADE"), nullable=False, comment="사용자 UUID (user.user_uuid 참조)", ) requested_amount: Mapped[int] = mapped_column( Integer, nullable=False, comment="요청 크레딧 수량 (양수)", ) message: Mapped[Optional[str]] = mapped_column( String(500), nullable=True, comment="사용자 요청 메시지", ) status: Mapped[str] = mapped_column( String(20), nullable=False, default=ChargeRequestStatus.PENDING, server_default="pending", comment="처리 상태 (pending/approved/rejected/cancelled)", ) admin_id: Mapped[Optional[int]] = mapped_column( BigInteger, ForeignKey("admin.id", ondelete="SET NULL"), nullable=True, comment="처리한 백오피스 관리자 ID", ) admin_note: Mapped[Optional[str]] = mapped_column( String(1000), nullable=True, comment="관리자 메모", ) processed_at: Mapped[Optional[datetime]] = mapped_column( DateTime, nullable=True, comment="처리 일시", ) created_at: Mapped[datetime] = mapped_column( DateTime, nullable=False, server_default=func.now(), comment="요청 일시", ) updated_at: Mapped[datetime] = mapped_column( DateTime, nullable=False, server_default=func.now(), onupdate=func.now(), comment="수정 일시", ) user: Mapped["User"] = relationship( "User", foreign_keys=[user_uuid], primaryjoin="CreditChargeRequest.user_uuid == User.user_uuid", back_populates="credit_requests", lazy="noload", ) transactions: Mapped[list["CreditTransaction"]] = relationship( "CreditTransaction", back_populates="charge_request", lazy="noload", ) admin: Mapped[Optional["Admin"]] = relationship( "Admin", foreign_keys=[admin_id], primaryjoin="CreditChargeRequest.admin_id == Admin.id", lazy="selectin", ) def __repr__(self) -> str: return ( f"" ) class CreditTransaction(Base): __tablename__ = "credit_transaction" __table_args__ = ( Index("idx_credit_tx_user_uuid", "user_uuid"), Index("idx_credit_tx_user_uuid_created", "user_uuid", "created_at"), Index("idx_credit_tx_type", "type"), Index("idx_credit_tx_related_request", "related_request_id"), { "mysql_engine": "InnoDB", "mysql_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci", }, ) id: Mapped[int] = mapped_column( BigInteger, primary_key=True, nullable=False, autoincrement=True, comment="고유 식별자", ) user_uuid: Mapped[str] = mapped_column( String(36), ForeignKey("user.user_uuid", ondelete="CASCADE"), nullable=False, comment="사용자 UUID", ) amount: Mapped[int] = mapped_column( Integer, nullable=False, comment="변경 크레딧 수량 (충전 양수, 차감 음수)", ) balance_after: Mapped[int] = mapped_column( Integer, nullable=False, comment="변경 직후 잔액", ) type: Mapped[str] = mapped_column( String(20), nullable=False, comment="변경 유형 (charge/consume/refund/admin_adjust)", ) reason: Mapped[Optional[str]] = mapped_column( String(255), nullable=True, comment="변경 사유", ) admin_id: Mapped[Optional[int]] = mapped_column( BigInteger, ForeignKey("admin.id", ondelete="SET NULL"), nullable=True, comment="처리 관리자 ID (관리자 충전/차감 시)", ) related_request_id: Mapped[Optional[int]] = mapped_column( BigInteger, ForeignKey("credit_charge_request.id", ondelete="SET NULL"), nullable=True, comment="연관 충전 요청 ID", ) created_at: Mapped[datetime] = mapped_column( DateTime, nullable=False, server_default=func.now(), comment="변경 일시", ) user: Mapped["User"] = relationship( "User", foreign_keys=[user_uuid], primaryjoin="CreditTransaction.user_uuid == User.user_uuid", back_populates="credit_transactions", lazy="noload", ) charge_request: Mapped[Optional[CreditChargeRequest]] = relationship( "CreditChargeRequest", back_populates="transactions", lazy="noload", ) admin: Mapped[Optional["Admin"]] = relationship( "Admin", foreign_keys=[admin_id], primaryjoin="CreditTransaction.admin_id == Admin.id", lazy="selectin", ) def __repr__(self) -> str: return ( f"" )