fix: 회원 상세 - transaction_id로 POS 품목 조회 연동
- 마일리지 적립 시 저장된 transaction_id로 SALE_SUB 조회 - 적립 내역에 구매 품목 표시 (품명, 수량, 가격) - 구매 이력 탭: QR 적립된 구매만 품목과 함께 표시 - 기존 전화번호→고객코드 매핑 로직 제거 (불필요)
This commit is contained in:
parent
7843ca8fcf
commit
8c3bcb525d
113
backend/app.py
113
backend/app.py
@ -3009,7 +3009,7 @@ def api_member_history(phone):
|
|||||||
"""
|
"""
|
||||||
회원 구매 이력 통합 조회 API
|
회원 구매 이력 통합 조회 API
|
||||||
- 마일리지 적립/사용 내역 (SQLite)
|
- 마일리지 적립/사용 내역 (SQLite)
|
||||||
- POS 구매 이력 (MSSQL)
|
- transaction_id로 POS 품목 조회 (MSSQL)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 전화번호 정규화
|
# 전화번호 정규화
|
||||||
@ -3022,6 +3022,8 @@ def api_member_history(phone):
|
|||||||
'purchases': []
|
'purchases': []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction_ids = [] # 적립된 거래번호 수집
|
||||||
|
|
||||||
# 1. 마일리지 내역 조회 (SQLite)
|
# 1. 마일리지 내역 조회 (SQLite)
|
||||||
try:
|
try:
|
||||||
sqlite_conn = db_manager.get_sqlite_connection()
|
sqlite_conn = db_manager.get_sqlite_connection()
|
||||||
@ -3037,76 +3039,57 @@ def api_member_history(phone):
|
|||||||
if user:
|
if user:
|
||||||
user_id = user['id']
|
user_id = user['id']
|
||||||
|
|
||||||
# 적립/사용 내역 조회
|
# 적립/사용 내역 조회 (claim_tokens 조인으로 금액 포함)
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
SELECT
|
SELECT
|
||||||
ml.points, ml.balance_after, ml.reason,
|
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
|
FROM mileage_ledger ml
|
||||||
|
LEFT JOIN claim_tokens ct ON ml.transaction_id = ct.transaction_id
|
||||||
WHERE ml.user_id = ?
|
WHERE ml.user_id = ?
|
||||||
ORDER BY ml.created_at DESC
|
ORDER BY ml.created_at DESC
|
||||||
LIMIT 50
|
LIMIT 50
|
||||||
""", (user_id,))
|
""", (user_id,))
|
||||||
transactions = cursor.fetchall()
|
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'] = {
|
result['mileage'] = {
|
||||||
'user_id': user_id,
|
'user_id': user_id,
|
||||||
'name': user['nickname'] or '',
|
'name': user['nickname'] or '',
|
||||||
'phone': user['phone'],
|
'phone': user['phone'],
|
||||||
'balance': user['mileage_balance'] or 0,
|
'balance': user['mileage_balance'] or 0,
|
||||||
'member_since': user['created_at'],
|
'member_since': user['created_at'],
|
||||||
'transactions': [{
|
'transactions': tx_list
|
||||||
'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]
|
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"마일리지 조회 실패: {e}")
|
logging.warning(f"마일리지 조회 실패: {e}")
|
||||||
|
|
||||||
# 2. POS 구매 이력 조회 (MSSQL)
|
# 2. transaction_id로 POS 품목 조회 (MSSQL)
|
||||||
try:
|
if transaction_ids and result['mileage']:
|
||||||
base_session = db_manager.get_session('PM_BASE')
|
try:
|
||||||
pres_session = db_manager.get_session('PM_PRES')
|
pres_session = db_manager.get_session('PM_PRES')
|
||||||
drug_session = db_manager.get_session('PM_DRUG')
|
|
||||||
|
|
||||||
# 전화번호로 고객코드 조회
|
# 각 거래번호별 품목 조회
|
||||||
cuscode_query = text("""
|
items_by_tx = {}
|
||||||
SELECT TOP 1 CUSCODE, PANAME
|
for tx_id in transaction_ids[:30]: # 최대 30건
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
# 구매 이력 조회 (최근 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_query = text("""
|
items_query = text("""
|
||||||
SELECT
|
SELECT
|
||||||
S.DrugCode,
|
S.DrugCode,
|
||||||
@ -3117,25 +3100,23 @@ def api_member_history(phone):
|
|||||||
LEFT JOIN PM_DRUG.dbo.CD_GOODS G ON S.DrugCode = G.DrugCode
|
LEFT JOIN PM_DRUG.dbo.CD_GOODS G ON S.DrugCode = G.DrugCode
|
||||||
WHERE S.SL_NO_order = :order_no
|
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({
|
items_by_tx[tx_id] = [{
|
||||||
'order_no': order.order_no,
|
'drug_code': item.DrugCode,
|
||||||
'date': order.order_date,
|
'name': item.GoodsName or '알 수 없음',
|
||||||
'total': float(order.total_amount) if order.total_amount else 0,
|
'quantity': float(item.quantity) if item.quantity else 1,
|
||||||
'discount': float(order.discount) if order.discount else 0,
|
'price': float(item.price) if item.price else 0
|
||||||
'items': [{
|
} for item in 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]
|
|
||||||
})
|
|
||||||
|
|
||||||
result['purchases'] = purchases
|
# 마일리지 내역에 품목 추가
|
||||||
|
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:
|
except Exception as e:
|
||||||
logging.warning(f"POS 구매 이력 조회 실패: {e}")
|
logging.warning(f"POS 품목 조회 실패: {e}")
|
||||||
|
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
||||||
|
|||||||
@ -906,6 +906,25 @@
|
|||||||
month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'
|
month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'
|
||||||
}) : '';
|
}) : '';
|
||||||
|
|
||||||
|
// 품목 렌더링
|
||||||
|
let itemsHtml = '';
|
||||||
|
if (tx.items && tx.items.length > 0) {
|
||||||
|
itemsHtml = `
|
||||||
|
<div class="tx-items">
|
||||||
|
${tx.items.map(item => `
|
||||||
|
<div class="tx-item">
|
||||||
|
<span class="tx-item-name">${escapeHtml(item.name)}</span>
|
||||||
|
<span class="tx-item-qty">x${item.quantity}</span>
|
||||||
|
<span class="tx-item-price">${item.price.toLocaleString()}원</span>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 금액 표시
|
||||||
|
const amountText = tx.total_amount ? ` (${tx.total_amount.toLocaleString()}원 구매)` : '';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="tx-card ${isPositive ? '' : 'negative'}">
|
<div class="tx-card ${isPositive ? '' : 'negative'}">
|
||||||
<div class="tx-header">
|
<div class="tx-header">
|
||||||
@ -914,26 +933,34 @@
|
|||||||
${isPositive ? '+' : ''}${tx.points.toLocaleString()}P
|
${isPositive ? '+' : ''}${tx.points.toLocaleString()}P
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tx-desc">${escapeHtml(tx.description || tx.reason || '')}</div>
|
<div class="tx-desc">${escapeHtml(tx.description || tx.reason || '')}${amountText}</div>
|
||||||
|
${itemsHtml}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderPurchaseTab(container) {
|
function renderPurchaseTab(container) {
|
||||||
if (!detailData.purchases || detailData.purchases.length === 0) {
|
// 적립 내역 중 품목이 있는 것들만 추출
|
||||||
container.innerHTML = '<div class="detail-empty">📭 구매 이력이 없습니다<br><small style="color:#94a3b8;">최근 30일 내역만 표시됩니다</small></div>';
|
if (!detailData.mileage || !detailData.mileage.transactions) {
|
||||||
|
container.innerHTML = '<div class="detail-empty">📭 구매 이력이 없습니다</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const purchases = detailData.mileage.transactions.filter(tx =>
|
||||||
|
tx.items && tx.items.length > 0 && tx.points > 0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (purchases.length === 0) {
|
||||||
|
container.innerHTML = '<div class="detail-empty">📭 QR 적립된 구매 이력이 없습니다</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const purchases = detailData.purchases;
|
|
||||||
container.innerHTML = purchases.map(p => {
|
container.innerHTML = purchases.map(p => {
|
||||||
// 날짜 포맷
|
// 날짜 포맷
|
||||||
const dateStr = p.date || '';
|
const date = p.created_at ? new Date(p.created_at).toLocaleString('ko-KR', {
|
||||||
let formattedDate = dateStr;
|
year: 'numeric', month: 'short', day: 'numeric'
|
||||||
if (dateStr.length === 8) {
|
}) : '';
|
||||||
formattedDate = `${dateStr.slice(0,4)}-${dateStr.slice(4,6)}-${dateStr.slice(6,8)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 품목 렌더링
|
// 품목 렌더링
|
||||||
const itemsHtml = (p.items || []).map(item => `
|
const itemsHtml = (p.items || []).map(item => `
|
||||||
@ -947,9 +974,10 @@
|
|||||||
return `
|
return `
|
||||||
<div class="purchase-card">
|
<div class="purchase-card">
|
||||||
<div class="purchase-header">
|
<div class="purchase-header">
|
||||||
<span class="purchase-date">📅 ${formattedDate}</span>
|
<span class="purchase-date">📅 ${date}</span>
|
||||||
<span class="purchase-total">${p.total.toLocaleString()}원</span>
|
<span class="purchase-total">${(p.total_amount || 0).toLocaleString()}원</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="font-size:12px;color:#10b981;margin-bottom:8px;">+${p.points.toLocaleString()}P 적립</div>
|
||||||
${p.items && p.items.length > 0 ? `
|
${p.items && p.items.length > 0 ? `
|
||||||
<div class="purchase-items">${itemsHtml}</div>
|
<div class="purchase-items">${itemsHtml}</div>
|
||||||
` : ''}
|
` : ''}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user