feat: 관리자 페이지 사용자 상세 이력 조회 기능 추가
- Flask 백엔드에 /admin/user/<user_id> API 엔드포인트 추가 - SQLite에서 사용자 정보, 마일리지 이력, 구매 이력 조회 - MSSQL에서 각 거래별 상품 상세 조회 (SALE_SUB + CD_GOODS JOIN) - "첫번째상품명 외 N개" 형식 요약 생성 - admin.html 사용자 테이블에 클릭 이벤트 추가 - 사용자 상세 모달 UI 구현 (탭 + 아코디언) - 탭: 구매 이력 / 적립 이력 분리 표시 - 아코디언: 각 구매 건 클릭 시 상품 목록 펼침/접기 - CSS 스타일 추가 (아코디언 애니메이션, 테이블 호버 효과) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
139
backend/app.py
139
backend/app.py
@@ -456,6 +456,145 @@ def admin_transaction_detail(transaction_id):
|
||||
}), 500
|
||||
|
||||
|
||||
@app.route('/admin/user/<int:user_id>')
|
||||
def admin_user_detail(user_id):
|
||||
"""사용자 상세 이력 조회 - 구매 이력, 적립 이력, 구매 품목"""
|
||||
try:
|
||||
# 1. SQLite 연결
|
||||
conn = db_manager.get_sqlite_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 2. 사용자 기본 정보 조회
|
||||
cursor.execute("""
|
||||
SELECT id, nickname, phone, mileage_balance, created_at
|
||||
FROM users WHERE id = ?
|
||||
""", (user_id,))
|
||||
user = cursor.fetchone()
|
||||
|
||||
if not user:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '사용자를 찾을 수 없습니다.'
|
||||
}), 404
|
||||
|
||||
# 3. 마일리지 이력 조회 (최근 50건)
|
||||
cursor.execute("""
|
||||
SELECT transaction_id, points, balance_after, reason, description, created_at
|
||||
FROM mileage_ledger
|
||||
WHERE user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 50
|
||||
""", (user_id,))
|
||||
mileage_history = cursor.fetchall()
|
||||
|
||||
# 4. 구매 이력 조회 (적립된 거래만, 최근 20건)
|
||||
cursor.execute("""
|
||||
SELECT transaction_id, total_amount, claimable_points, claimed_at
|
||||
FROM claim_tokens
|
||||
WHERE claimed_by_user_id = ?
|
||||
ORDER BY claimed_at DESC
|
||||
LIMIT 20
|
||||
""", (user_id,))
|
||||
claimed_tokens = cursor.fetchall()
|
||||
|
||||
# 5. 각 거래의 상품 상세 조회 (MSSQL)
|
||||
purchases = []
|
||||
|
||||
try:
|
||||
session = db_manager.get_session('PM_PRES')
|
||||
|
||||
for token in claimed_tokens:
|
||||
transaction_id = token['transaction_id']
|
||||
|
||||
# SALE_SUB + CD_GOODS JOIN
|
||||
sale_items_query = text("""
|
||||
SELECT
|
||||
S.DrugCode,
|
||||
ISNULL(G.GoodsName, '(약품명 없음)') AS goods_name,
|
||||
S.SL_NM_item AS quantity,
|
||||
S.SL_NM_cost_a AS price,
|
||||
S.SL_TOTAL_PRICE AS total
|
||||
FROM SALE_SUB S
|
||||
LEFT JOIN PM_DRUG.dbo.CD_GOODS G ON S.DrugCode = G.DrugCode
|
||||
WHERE S.SL_NO_order = :transaction_id
|
||||
ORDER BY S.DrugCode
|
||||
""")
|
||||
|
||||
items_raw = session.execute(
|
||||
sale_items_query,
|
||||
{'transaction_id': transaction_id}
|
||||
).fetchall()
|
||||
|
||||
# 상품 리스트 변환
|
||||
items = [
|
||||
{
|
||||
'code': item.DrugCode,
|
||||
'name': item.goods_name,
|
||||
'qty': int(item.quantity or 0),
|
||||
'price': int(item.price or 0),
|
||||
'total': int(item.total or 0)
|
||||
}
|
||||
for item in items_raw
|
||||
]
|
||||
|
||||
# 상품 요약 생성 ("첫번째상품명 외 N개")
|
||||
if items:
|
||||
first_item_name = items[0]['name']
|
||||
items_count = len(items)
|
||||
if items_count == 1:
|
||||
items_summary = first_item_name
|
||||
else:
|
||||
items_summary = f"{first_item_name} 외 {items_count - 1}개"
|
||||
else:
|
||||
items_summary = "상품 정보 없음"
|
||||
items_count = 0
|
||||
|
||||
purchases.append({
|
||||
'transaction_id': transaction_id,
|
||||
'date': str(token['claimed_at'])[:16].replace('T', ' '),
|
||||
'amount': int(token['total_amount']),
|
||||
'points': int(token['claimable_points']),
|
||||
'items_summary': items_summary,
|
||||
'items_count': items_count,
|
||||
'items': items
|
||||
})
|
||||
|
||||
except Exception as mssql_error:
|
||||
# MSSQL 연결 실패 시 빈 배열 반환
|
||||
print(f"[WARNING] MSSQL 조회 실패 (user {user_id}): {mssql_error}")
|
||||
purchases = []
|
||||
|
||||
# 6. 응답 생성
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'user': {
|
||||
'id': user['id'],
|
||||
'name': user['nickname'],
|
||||
'phone': user['phone'],
|
||||
'balance': user['mileage_balance'],
|
||||
'created_at': str(user['created_at'])[:16].replace('T', ' ')
|
||||
},
|
||||
'mileage_history': [
|
||||
{
|
||||
'points': ml['points'],
|
||||
'balance_after': ml['balance_after'],
|
||||
'reason': ml['reason'],
|
||||
'description': ml['description'],
|
||||
'created_at': str(ml['created_at'])[:16].replace('T', ' '),
|
||||
'transaction_id': ml['transaction_id']
|
||||
}
|
||||
for ml in mileage_history
|
||||
],
|
||||
'purchases': purchases
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'조회 실패: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
@app.route('/admin')
|
||||
def admin():
|
||||
"""관리자 페이지 - 전체 사용자 및 적립 현황"""
|
||||
|
||||
Reference in New Issue
Block a user