diff --git a/backend/app.py b/backend/app.py index 86ba9c4..bb99d5e 100644 --- a/backend/app.py +++ b/backend/app.py @@ -3009,7 +3009,7 @@ def api_member_history(phone): """ 회원 구매 이력 통합 조회 API - 마일리지 적립/사용 내역 (SQLite) - - POS 구매 이력 (MSSQL) + - transaction_id로 POS 품목 조회 (MSSQL) """ try: # 전화번호 정규화 @@ -3022,6 +3022,8 @@ def api_member_history(phone): 'purchases': [] } + transaction_ids = [] # 적립된 거래번호 수집 + # 1. 마일리지 내역 조회 (SQLite) try: sqlite_conn = db_manager.get_sqlite_connection() @@ -3037,76 +3039,57 @@ def api_member_history(phone): if user: user_id = user['id'] - # 적립/사용 내역 조회 + # 적립/사용 내역 조회 (claim_tokens 조인으로 금액 포함) cursor.execute(""" SELECT ml.points, ml.balance_after, ml.reason, - ml.description, ml.transaction_id, ml.created_at + ml.description, ml.transaction_id, ml.created_at, + ct.total_amount FROM mileage_ledger ml + LEFT JOIN claim_tokens ct ON ml.transaction_id = ct.transaction_id WHERE ml.user_id = ? ORDER BY ml.created_at DESC LIMIT 50 """, (user_id,)) transactions = cursor.fetchall() + tx_list = [] + for t in transactions: + tx_data = { + 'points': t['points'], + 'balance_after': t['balance_after'], + 'reason': t['reason'], + 'description': t['description'], + 'transaction_id': t['transaction_id'], + 'created_at': t['created_at'], + 'total_amount': t['total_amount'], + 'items': [] # 나중에 POS에서 채움 + } + tx_list.append(tx_data) + + # 적립 거래번호 수집 + if t['transaction_id'] and t['reason'] == 'CLAIM': + transaction_ids.append(t['transaction_id']) + result['mileage'] = { 'user_id': user_id, 'name': user['nickname'] or '', 'phone': user['phone'], 'balance': user['mileage_balance'] or 0, 'member_since': user['created_at'], - 'transactions': [{ - 'points': t['points'], - 'balance_after': t['balance_after'], - 'reason': t['reason'], - 'description': t['description'], - 'transaction_id': t['transaction_id'], - 'created_at': t['created_at'] - } for t in transactions] + 'transactions': tx_list } except Exception as e: logging.warning(f"마일리지 조회 실패: {e}") - # 2. POS 구매 이력 조회 (MSSQL) - try: - base_session = db_manager.get_session('PM_BASE') - pres_session = db_manager.get_session('PM_PRES') - drug_session = db_manager.get_session('PM_DRUG') - - # 전화번호로 고객코드 조회 - cuscode_query = text(""" - SELECT TOP 1 CUSCODE, PANAME - FROM CD_PERSON - WHERE REPLACE(REPLACE(PHONE, '-', ''), ' ', '') = :phone - OR REPLACE(REPLACE(TEL_NO, '-', ''), ' ', '') = :phone - OR REPLACE(REPLACE(PHONE2, '-', ''), ' ', '') = :phone - """) - cus_row = base_session.execute(cuscode_query, {'phone': phone}).fetchone() - - if cus_row: - cuscode = cus_row.CUSCODE - result['pos_customer'] = { - 'cuscode': cuscode, - 'name': cus_row.PANAME - } + # 2. transaction_id로 POS 품목 조회 (MSSQL) + if transaction_ids and result['mileage']: + try: + pres_session = db_manager.get_session('PM_PRES') - # 구매 이력 조회 (최근 30일) - purchase_query = text(""" - SELECT - M.SL_NO_order as order_no, - M.SL_DT_appl as order_date, - M.SL_MY_total as total_amount, - M.SL_MY_discount as discount - FROM SALE_MAIN M - WHERE M.SL_CD_custom = :cuscode - AND M.SL_DT_appl >= CONVERT(VARCHAR(8), DATEADD(DAY, -30, GETDATE()), 112) - ORDER BY M.SL_DT_appl DESC, M.SL_NO_order DESC - """) - orders = pres_session.execute(purchase_query, {'cuscode': cuscode}).fetchall() - - purchases = [] - for order in orders[:20]: # 최대 20건 - # 주문 상세 (품목) + # 각 거래번호별 품목 조회 + items_by_tx = {} + for tx_id in transaction_ids[:30]: # 최대 30건 items_query = text(""" SELECT S.DrugCode, @@ -3117,25 +3100,23 @@ def api_member_history(phone): LEFT JOIN PM_DRUG.dbo.CD_GOODS G ON S.DrugCode = G.DrugCode WHERE S.SL_NO_order = :order_no """) - items = pres_session.execute(items_query, {'order_no': order.order_no}).fetchall() + items = pres_session.execute(items_query, {'order_no': tx_id}).fetchall() - purchases.append({ - 'order_no': order.order_no, - 'date': order.order_date, - 'total': float(order.total_amount) if order.total_amount else 0, - 'discount': float(order.discount) if order.discount else 0, - 'items': [{ - 'drug_code': item.DrugCode, - 'name': item.GoodsName or '알 수 없음', - 'quantity': float(item.quantity) if item.quantity else 1, - 'price': float(item.price) if item.price else 0 - } for item in items] - }) + items_by_tx[tx_id] = [{ + 'drug_code': item.DrugCode, + 'name': item.GoodsName or '알 수 없음', + 'quantity': float(item.quantity) if item.quantity else 1, + 'price': float(item.price) if item.price else 0 + } for item in items] - result['purchases'] = purchases - - except Exception as e: - logging.warning(f"POS 구매 이력 조회 실패: {e}") + # 마일리지 내역에 품목 추가 + for tx in result['mileage']['transactions']: + tx_id = tx.get('transaction_id') + if tx_id and tx_id in items_by_tx: + tx['items'] = items_by_tx[tx_id] + + except Exception as e: + logging.warning(f"POS 품목 조회 실패: {e}") return jsonify(result) diff --git a/backend/templates/admin_members.html b/backend/templates/admin_members.html index 020134d..ff35749 100644 --- a/backend/templates/admin_members.html +++ b/backend/templates/admin_members.html @@ -906,6 +906,25 @@ month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : ''; + // 품목 렌더링 + let itemsHtml = ''; + if (tx.items && tx.items.length > 0) { + itemsHtml = ` +
+ ${tx.items.map(item => ` +
+ ${escapeHtml(item.name)} + x${item.quantity} + ${item.price.toLocaleString()}원 +
+ `).join('')} +
+ `; + } + + // 금액 표시 + const amountText = tx.total_amount ? ` (${tx.total_amount.toLocaleString()}원 구매)` : ''; + return `
@@ -914,26 +933,34 @@ ${isPositive ? '+' : ''}${tx.points.toLocaleString()}P
-
${escapeHtml(tx.description || tx.reason || '')}
+
${escapeHtml(tx.description || tx.reason || '')}${amountText}
+ ${itemsHtml} `; }).join(''); } function renderPurchaseTab(container) { - if (!detailData.purchases || detailData.purchases.length === 0) { - container.innerHTML = '
📭 구매 이력이 없습니다
최근 30일 내역만 표시됩니다
'; + // 적립 내역 중 품목이 있는 것들만 추출 + if (!detailData.mileage || !detailData.mileage.transactions) { + container.innerHTML = '
📭 구매 이력이 없습니다
'; + return; + } + + const purchases = detailData.mileage.transactions.filter(tx => + tx.items && tx.items.length > 0 && tx.points > 0 + ); + + if (purchases.length === 0) { + container.innerHTML = '
📭 QR 적립된 구매 이력이 없습니다
'; return; } - const purchases = detailData.purchases; container.innerHTML = purchases.map(p => { // 날짜 포맷 - const dateStr = p.date || ''; - let formattedDate = dateStr; - if (dateStr.length === 8) { - formattedDate = `${dateStr.slice(0,4)}-${dateStr.slice(4,6)}-${dateStr.slice(6,8)}`; - } + const date = p.created_at ? new Date(p.created_at).toLocaleString('ko-KR', { + year: 'numeric', month: 'short', day: 'numeric' + }) : ''; // 품목 렌더링 const itemsHtml = (p.items || []).map(item => ` @@ -947,9 +974,10 @@ return `
- 📅 ${formattedDate} - ${p.total.toLocaleString()}원 + 📅 ${date} + ${(p.total_amount || 0).toLocaleString()}원
+
+${p.points.toLocaleString()}P 적립
${p.items && p.items.length > 0 ? `
${itemsHtml}
` : ''}