import logging from sqlalchemy import select from sqlalchemy.orm import outerjoin from sqladmin import ModelView, action from starlette.requests import Request from starlette.responses import RedirectResponse from app.backoffice.admin.models import Admin from app.credit.models import CreditChargeRequest, CreditTransaction from app.credit.services.credit_service import approve_charge_request, reject_charge_request from app.database.session import AsyncSessionLocal logger = logging.getLogger(__name__) class CreditChargeRequestAdmin(ModelView, model=CreditChargeRequest): name = "충전 요청" name_plural = "충전 요청 목록" icon = "fa-solid fa-coins" category = "크레딧 관리" page_size = 20 column_list = [ "id", "user_uuid", "requested_amount", "status", "admin.name", "processed_at", "created_at", ] column_details_list = [ "id", "user_uuid", "requested_amount", "message", "status", "admin.name", "admin_note", "processed_at", "created_at", "updated_at", ] form_columns = ["admin_note"] can_create = False column_searchable_list = [ CreditChargeRequest.user_uuid, CreditChargeRequest.status, ] column_default_sort = (CreditChargeRequest.created_at, True) column_sortable_list = [ CreditChargeRequest.id, CreditChargeRequest.user_uuid, CreditChargeRequest.requested_amount, CreditChargeRequest.status, CreditChargeRequest.processed_at, CreditChargeRequest.created_at, ] column_labels = { "id": "ID", "user_uuid": "사용자 UUID", "requested_amount": "요청 크레딧", "message": "사용자 메시지", "status": "상태", "admin.name": "처리 관리자", "admin_note": "관리자 메모", "processed_at": "처리일시", "created_at": "요청일시", "updated_at": "수정일시", } @action( name="approve_request", label="승인", confirmation_message="선택한 충전 요청을 승인하시겠습니까?", add_in_detail=True, add_in_list=True, ) async def approve_action(self, request: Request) -> RedirectResponse: admin_id = request.session.get("admin_id") pks = request.query_params.get("pks", "") async with AsyncSessionLocal() as session: for pk in pks.split(","): if not pk.strip(): continue try: await approve_charge_request( session=session, request_id=int(pk), admin_id=admin_id, ) await session.commit() except Exception as e: await session.rollback() logger.warning(f"[CREDIT-ADMIN] approve failed request_id={pk} error={e}") return RedirectResponse(request.url_for("admin:list", identity=self.identity), status_code=302) @action( name="reject_request", label="반려", confirmation_message="선택한 충전 요청을 반려하시겠습니까?", add_in_detail=True, add_in_list=True, ) async def reject_action(self, request: Request) -> RedirectResponse: admin_id = request.session.get("admin_id") pks = request.query_params.get("pks", "") async with AsyncSessionLocal() as session: for pk in pks.split(","): if not pk.strip(): continue try: await reject_charge_request( session=session, request_id=int(pk), admin_id=admin_id, ) await session.commit() except Exception as e: await session.rollback() logger.warning(f"[CREDIT-ADMIN] reject failed request_id={pk} error={e}") return RedirectResponse(request.url_for("admin:list", identity=self.identity), status_code=302) class CreditTransactionAdmin(ModelView, model=CreditTransaction): name = "크레딧 변경" name_plural = "크레딧 변경 목록" icon = "fa-solid fa-clock-rotate-left" category = "크레딧 관리" page_size = 50 can_create = False can_edit = False can_delete = False column_list = [ "id", "user_uuid", "amount", "balance_after", "type", "admin.name", "related_request_id", "created_at", ] column_details_list = [ "id", "user_uuid", "amount", "balance_after", "type", "reason", "admin.name", "related_request_id", "created_at", ] column_searchable_list = [ CreditTransaction.user_uuid, CreditTransaction.type, ] column_default_sort = (CreditTransaction.created_at, True) column_sortable_list = [ CreditTransaction.id, CreditTransaction.user_uuid, CreditTransaction.amount, CreditTransaction.type, CreditTransaction.created_at, ] column_labels = { "id": "ID", "user_uuid": "사용자 UUID", "amount": "변경 크레딧", "balance_after": "변경 후 잔액", "type": "변경 유형", "reason": "사유", "admin.name": "처리 관리자", "related_request_id": "충전 요청 ID", "created_at": "변경 일시", } def list_query(self, _request: Request): return ( select(CreditTransaction) .select_from(outerjoin(CreditTransaction, Admin, CreditTransaction.admin_id == Admin.id)) )