feat: 대시보드 모달에 조제 이력 탭 추가
- /admin/user/<id> API에 prescriptions 필드 추가
- 전화번호 → CD_PERSON(CUSCODE) → PS_main 연동
- 모달에 💊 조제 탭 추가 (admin_members.html 스타일 적용)
- 병원명, 의사명, 투약일수, 처방품목 표시
This commit is contained in:
parent
c33d857fa6
commit
3fc9bbaf8e
@ -1387,7 +1387,68 @@ def admin_user_detail(user_id):
|
|||||||
print(f"[WARNING] MSSQL 조회 실패 (user {user_id}): {mssql_error}")
|
print(f"[WARNING] MSSQL 조회 실패 (user {user_id}): {mssql_error}")
|
||||||
purchases = []
|
purchases = []
|
||||||
|
|
||||||
# 6. 응답 생성
|
# 6. 조제 이력 조회 (전화번호 → CUSCODE → PS_main)
|
||||||
|
prescriptions = []
|
||||||
|
pos_customer = None
|
||||||
|
if user['phone']:
|
||||||
|
try:
|
||||||
|
phone_clean = user['phone'].replace('-', '').replace(' ', '')
|
||||||
|
base_session = db_manager.get_session('PM_BASE')
|
||||||
|
pres_session = db_manager.get_session('PM_PRES')
|
||||||
|
|
||||||
|
# 전화번호로 CUSCODE 조회
|
||||||
|
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_clean}).fetchone()
|
||||||
|
|
||||||
|
if cus_row:
|
||||||
|
cuscode = cus_row.CUSCODE
|
||||||
|
pos_customer = {'cuscode': cuscode, 'name': cus_row.PANAME}
|
||||||
|
|
||||||
|
# 조제 이력 조회
|
||||||
|
rx_query = text("""
|
||||||
|
SELECT TOP 30
|
||||||
|
P.PreSerial, P.Indate, P.Paname, P.Drname, P.OrderName, P.TDAYS
|
||||||
|
FROM PS_main P
|
||||||
|
WHERE P.CusCode = :cuscode
|
||||||
|
ORDER BY P.Indate DESC, P.PreSerial DESC
|
||||||
|
""")
|
||||||
|
rxs = pres_session.execute(rx_query, {'cuscode': cuscode}).fetchall()
|
||||||
|
|
||||||
|
for rx in rxs:
|
||||||
|
# 처방 품목 조회
|
||||||
|
items_query = text("""
|
||||||
|
SELECT S.DrugCode, G.GoodsName, S.Days, S.QUAN, S.QUAN_TIME
|
||||||
|
FROM PS_sub_pharm S
|
||||||
|
LEFT JOIN PM_DRUG.dbo.CD_GOODS G ON S.DrugCode = G.DrugCode
|
||||||
|
WHERE S.PreSerial = :pre_serial
|
||||||
|
""")
|
||||||
|
items = pres_session.execute(items_query, {'pre_serial': rx.PreSerial}).fetchall()
|
||||||
|
|
||||||
|
prescriptions.append({
|
||||||
|
'pre_serial': rx.PreSerial,
|
||||||
|
'date': rx.Indate,
|
||||||
|
'patient_name': rx.Paname,
|
||||||
|
'doctor': rx.Drname,
|
||||||
|
'hospital': rx.OrderName,
|
||||||
|
'total_days': rx.TDAYS,
|
||||||
|
'items': [{
|
||||||
|
'drug_code': item.DrugCode,
|
||||||
|
'name': item.GoodsName or '알 수 없음',
|
||||||
|
'days': float(item.Days) if item.Days else 0,
|
||||||
|
'quantity': float(item.QUAN) if item.QUAN else 0,
|
||||||
|
'times_per_day': float(item.QUAN_TIME) if item.QUAN_TIME else 0
|
||||||
|
} for item in items]
|
||||||
|
})
|
||||||
|
except Exception as rx_error:
|
||||||
|
logging.warning(f"조제 이력 조회 실패 (user {user_id}): {rx_error}")
|
||||||
|
|
||||||
|
# 7. 응답 생성
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': True,
|
'success': True,
|
||||||
'user': {
|
'user': {
|
||||||
@ -1409,7 +1470,9 @@ def admin_user_detail(user_id):
|
|||||||
}
|
}
|
||||||
for ml in mileage_history
|
for ml in mileage_history
|
||||||
],
|
],
|
||||||
'purchases': purchases
|
'purchases': purchases,
|
||||||
|
'prescriptions': prescriptions,
|
||||||
|
'pos_customer': pos_customer
|
||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@ -896,10 +896,13 @@
|
|||||||
<!-- 탭 메뉴 -->
|
<!-- 탭 메뉴 -->
|
||||||
<div style="display: flex; gap: 16px; margin-bottom: 16px; border-bottom: 2px solid #e9ecef;">
|
<div style="display: flex; gap: 16px; margin-bottom: 16px; border-bottom: 2px solid #e9ecef;">
|
||||||
<button onclick="switchTab('purchases')" id="tab-purchases" class="tab-btn" style="padding: 12px 20px; border: none; background: none; font-size: 15px; font-weight: 600; cursor: pointer; border-bottom: 3px solid #6366f1; color: #6366f1;">
|
<button onclick="switchTab('purchases')" id="tab-purchases" class="tab-btn" style="padding: 12px 20px; border: none; background: none; font-size: 15px; font-weight: 600; cursor: pointer; border-bottom: 3px solid #6366f1; color: #6366f1;">
|
||||||
구매 이력 (${purchases.length})
|
🛒 구매 (${purchases.length})
|
||||||
</button>
|
</button>
|
||||||
<button onclick="switchTab('mileage')" id="tab-mileage" class="tab-btn" style="padding: 12px 20px; border: none; background: none; font-size: 15px; font-weight: 600; cursor: pointer; border-bottom: 3px solid transparent; color: #868e96;">
|
<button onclick="switchTab('mileage')" id="tab-mileage" class="tab-btn" style="padding: 12px 20px; border: none; background: none; font-size: 15px; font-weight: 600; cursor: pointer; border-bottom: 3px solid transparent; color: #868e96;">
|
||||||
적립 이력 (${mileageHistory.length})
|
💰 적립 (${mileageHistory.length})
|
||||||
|
</button>
|
||||||
|
<button onclick="switchTab('prescriptions')" id="tab-prescriptions" class="tab-btn" style="padding: 12px 20px; border: none; background: none; font-size: 15px; font-weight: 600; cursor: pointer; border-bottom: 3px solid transparent; color: #868e96;">
|
||||||
|
💊 조제 (${data.prescriptions ? data.prescriptions.length : 0})
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -956,6 +959,56 @@
|
|||||||
html += '<p style="text-align: center; padding: 40px; color: #868e96;">적립 이력이 없습니다.</p>';
|
html += '<p style="text-align: center; padding: 40px; color: #868e96;">적립 이력이 없습니다.</p>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 조제 이력 탭 -->
|
||||||
|
<div id="tab-content-prescriptions" class="tab-content" style="display: none;">
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 조제 이력 렌더링
|
||||||
|
const prescriptions = data.prescriptions || [];
|
||||||
|
if (prescriptions.length > 0) {
|
||||||
|
prescriptions.forEach(rx => {
|
||||||
|
// 날짜 포맷
|
||||||
|
const dateStr = rx.date || '';
|
||||||
|
let formattedDate = dateStr;
|
||||||
|
if (dateStr.length === 8) {
|
||||||
|
formattedDate = `${dateStr.slice(0,4)}.${dateStr.slice(4,6)}.${dateStr.slice(6,8)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 처방 품목
|
||||||
|
const itemsHtml = (rx.items || []).map(item => {
|
||||||
|
const dosage = item.quantity || 1;
|
||||||
|
const freq = item.times_per_day || 1;
|
||||||
|
const days = item.days || 0;
|
||||||
|
return `
|
||||||
|
<div style="display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #f1f3f5;">
|
||||||
|
<span style="color: #495057; font-size: 14px;">${item.name}</span>
|
||||||
|
<span style="color: #6366f1; font-size: 13px; font-weight: 600;">${dosage}정 × ${freq}회 × ${days}일</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<div style="border: 1px solid #e9ecef; border-radius: 12px; margin-bottom: 12px; padding: 16px; border-left: 4px solid #6366f1;">
|
||||||
|
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
|
||||||
|
<span style="font-size: 15px; font-weight: 600; color: #212529;">📅 ${formattedDate}</span>
|
||||||
|
<span style="font-size: 13px; color: #6366f1; font-weight: 600;">${rx.total_days || ''}일분</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 13px; color: #64748b; margin-bottom: 12px;">
|
||||||
|
🏥 ${rx.hospital || ''} · ${rx.doctor || ''}
|
||||||
|
</div>
|
||||||
|
${rx.items && rx.items.length > 0 ? `<div style="background: #f8f9fa; border-radius: 8px; padding: 12px;">${itemsHtml}</div>` : ''}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
} else if (!data.pos_customer) {
|
||||||
|
html += '<p style="text-align: center; padding: 40px; color: #868e96;">📭 POS 회원으로 등록되지 않았습니다<br><small>전화번호가 POS에 등록되면 조제 이력이 표시됩니다</small></p>';
|
||||||
|
} else {
|
||||||
|
html += '<p style="text-align: center; padding: 40px; color: #868e96;">📭 조제 이력이 없습니다</p>';
|
||||||
|
}
|
||||||
|
|
||||||
html += `
|
html += `
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user