o2o-castad-backend/app/credit/models.py

244 lines
6.6 KiB
Python

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"<CreditChargeRequest("
f"id={self.id}, user_uuid='{self.user_uuid}', "
f"amount={self.requested_amount}, status='{self.status}'"
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"<CreditTransaction("
f"id={self.id}, user_uuid='{self.user_uuid}', "
f"amount={self.amount}, type='{self.type}'"
f")>"
)