feat: PMR 이전 처방 비교 기능
- /pmr/api/patient/<cus_code>/history: 환자 이전 처방 이력 API - 하단에 이전 처방 영역 + < > 네비게이션 - 현재 처방과 이전 처방 한눈에 비교 가능
This commit is contained in:
parent
fc2db78816
commit
6192f635ca
@ -518,3 +518,113 @@ def create_label_image(patient_name, med_name, add_info='', dosage=0, frequency=
|
|||||||
draw_centered("청 춘 약 국", pharmacy_y, info_font)
|
draw_centered("청 춘 약 국", pharmacy_y, info_font)
|
||||||
|
|
||||||
return image
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# API: 환자 이전 처방 이력
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
@pmr_bp.route('/api/patient/<cus_code>/history', methods=['GET'])
|
||||||
|
def get_patient_history(cus_code):
|
||||||
|
"""
|
||||||
|
환자 이전 처방 이력 조회
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cus_code: 환자 고유코드 (CusCode)
|
||||||
|
|
||||||
|
Query Params:
|
||||||
|
- limit: 최대 조회 건수 (기본 10, 최대 50)
|
||||||
|
- exclude: 제외할 처방번호 (현재 처방)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
limit = min(int(request.args.get('limit', 10)), 50)
|
||||||
|
exclude_serial = request.args.get('exclude', '')
|
||||||
|
|
||||||
|
conn = get_mssql_connection('PM_PRES')
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# 이전 처방 목록 조회
|
||||||
|
query = """
|
||||||
|
SELECT TOP (?)
|
||||||
|
m.PreSerial,
|
||||||
|
m.PassDay,
|
||||||
|
m.PresTime,
|
||||||
|
m.Paname,
|
||||||
|
m.InsName,
|
||||||
|
m.Drname,
|
||||||
|
m.PRICE_P,
|
||||||
|
(SELECT COUNT(*) FROM PS_sub_pharm WHERE PreSerial = m.PreSerial) as drug_count
|
||||||
|
FROM PS_MAIN m
|
||||||
|
WHERE m.CusCode = ?
|
||||||
|
"""
|
||||||
|
params = [limit, cus_code]
|
||||||
|
|
||||||
|
if exclude_serial:
|
||||||
|
query += " AND m.PreSerial != ?"
|
||||||
|
params.append(exclude_serial)
|
||||||
|
|
||||||
|
query += " ORDER BY m.PassDay DESC, m.PresTime DESC"
|
||||||
|
|
||||||
|
cursor.execute(query, params)
|
||||||
|
|
||||||
|
history = []
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
pre_serial = row.PreSerial
|
||||||
|
|
||||||
|
# 해당 처방의 약품 목록 조회
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT
|
||||||
|
s.DrugCode,
|
||||||
|
s.Days,
|
||||||
|
s.QUAN,
|
||||||
|
s.QUAN_TIME,
|
||||||
|
g.GoodsName,
|
||||||
|
m.PRINT_TYPE
|
||||||
|
FROM PS_sub_pharm s
|
||||||
|
LEFT JOIN PM_DRUG.dbo.CD_GOODS g ON s.DrugCode = g.DrugCode
|
||||||
|
LEFT JOIN PM_DRUG.dbo.CD_MC m ON s.DrugCode = m.DRUGCODE
|
||||||
|
WHERE s.PreSerial = ?
|
||||||
|
ORDER BY s.SUB_SERIAL
|
||||||
|
""", (pre_serial,))
|
||||||
|
|
||||||
|
medications = []
|
||||||
|
for med_row in cursor.fetchall():
|
||||||
|
medications.append({
|
||||||
|
'medication_code': med_row.DrugCode or '',
|
||||||
|
'med_name': med_row.GoodsName or med_row.DrugCode or '',
|
||||||
|
'add_info': med_row.PRINT_TYPE or '',
|
||||||
|
'dosage': float(med_row.QUAN) if med_row.QUAN else 0,
|
||||||
|
'frequency': med_row.QUAN_TIME or 0,
|
||||||
|
'duration': med_row.Days or 0
|
||||||
|
})
|
||||||
|
|
||||||
|
# 날짜 포맷
|
||||||
|
pass_day = row.PassDay
|
||||||
|
if pass_day and len(pass_day) == 8:
|
||||||
|
date_formatted = f"{pass_day[:4]}-{pass_day[4:6]}-{pass_day[6:8]}"
|
||||||
|
else:
|
||||||
|
date_formatted = pass_day or ''
|
||||||
|
|
||||||
|
history.append({
|
||||||
|
'prescription_id': pre_serial,
|
||||||
|
'date': date_formatted,
|
||||||
|
'time': row.PresTime or '',
|
||||||
|
'patient_name': row.Paname or '',
|
||||||
|
'hospital': row.InsName or '',
|
||||||
|
'doctor': row.Drname or '',
|
||||||
|
'copayment': int(row.PRICE_P or 0),
|
||||||
|
'medication_count': row.drug_count or 0,
|
||||||
|
'medications': medications
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'cus_code': cus_code,
|
||||||
|
'count': len(history),
|
||||||
|
'history': history
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"환자 이전 처방 조회 오류: {e}")
|
||||||
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|||||||
@ -242,6 +242,75 @@
|
|||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
}
|
}
|
||||||
@keyframes spin { to { transform: rotate(360deg); } }
|
@keyframes spin { to { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
/* 이전 처방 비교 영역 */
|
||||||
|
.history-section {
|
||||||
|
border-top: 3px solid #e2e8f0;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
.history-header {
|
||||||
|
background: linear-gradient(135deg, #64748b, #94a3b8);
|
||||||
|
color: #fff;
|
||||||
|
padding: 12px 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.history-header .title { font-weight: 600; }
|
||||||
|
.history-nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.history-nav button {
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.history-nav button:hover:not(:disabled) { background: rgba(255,255,255,0.4); }
|
||||||
|
.history-nav button:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||||
|
.history-nav .index { font-size: 0.9rem; }
|
||||||
|
.history-info {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #f1f5f9;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #64748b;
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.history-meds {
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
.history-meds table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.history-meds th {
|
||||||
|
background: #e2e8f0;
|
||||||
|
padding: 8px 10px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
.history-meds td {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
.history-meds .med-name { font-weight: 500; }
|
||||||
|
.history-empty {
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -310,11 +379,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 이전 처방 비교 -->
|
||||||
|
<div class="history-section" id="historySection" style="display:none;">
|
||||||
|
<div class="history-header">
|
||||||
|
<span class="title">📜 이전 처방</span>
|
||||||
|
<div class="history-nav">
|
||||||
|
<button onclick="prevHistory()" id="historyPrev" disabled>‹</button>
|
||||||
|
<span class="index" id="historyIndex">-</span>
|
||||||
|
<button onclick="nextHistory()" id="historyNext" disabled>›</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="history-info" id="historyInfo"></div>
|
||||||
|
<div class="history-meds" id="historyMeds">
|
||||||
|
<div class="history-empty">이전 처방 없음</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let currentPrescriptionId = null;
|
let currentPrescriptionId = null;
|
||||||
|
let currentPatientCode = null;
|
||||||
|
let historyData = [];
|
||||||
|
let historyIndex = 0;
|
||||||
|
|
||||||
// HTML 이스케이프
|
// HTML 이스케이프
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
@ -458,16 +546,117 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('actionBar').style.display = 'flex';
|
document.getElementById('actionBar').style.display = 'flex';
|
||||||
|
|
||||||
|
// 이전 처방 로드
|
||||||
|
currentPatientCode = data.patient.code;
|
||||||
|
if (currentPatientCode) {
|
||||||
|
loadPatientHistory(currentPatientCode, prescriptionId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
medList.innerHTML = `<div class="empty-state"><div class="text">오류: ${err.message}</div></div>`;
|
medList.innerHTML = `<div class="empty-state"><div class="text">오류: ${err.message}</div></div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 환자 이전 처방 로드
|
||||||
|
async function loadPatientHistory(cusCode, excludeSerial) {
|
||||||
|
const section = document.getElementById('historySection');
|
||||||
|
const medsDiv = document.getElementById('historyMeds');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/pmr/api/patient/${cusCode}/history?limit=10&exclude=${excludeSerial}`);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.success && data.history.length > 0) {
|
||||||
|
historyData = data.history;
|
||||||
|
historyIndex = 0;
|
||||||
|
section.style.display = 'block';
|
||||||
|
renderHistory();
|
||||||
|
} else {
|
||||||
|
section.style.display = 'none';
|
||||||
|
historyData = [];
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('History error:', err);
|
||||||
|
section.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 이전 처방 렌더링
|
||||||
|
function renderHistory() {
|
||||||
|
if (historyData.length === 0) return;
|
||||||
|
|
||||||
|
const h = historyData[historyIndex];
|
||||||
|
|
||||||
|
// 인덱스 표시
|
||||||
|
document.getElementById('historyIndex').textContent =
|
||||||
|
`${historyIndex + 1} / ${historyData.length}`;
|
||||||
|
|
||||||
|
// 네비게이션 버튼
|
||||||
|
document.getElementById('historyPrev').disabled = historyIndex <= 0;
|
||||||
|
document.getElementById('historyNext').disabled = historyIndex >= historyData.length - 1;
|
||||||
|
|
||||||
|
// 정보
|
||||||
|
document.getElementById('historyInfo').innerHTML = `
|
||||||
|
<span>📅 ${h.date}</span>
|
||||||
|
<span>🏥 ${h.hospital || '-'}</span>
|
||||||
|
<span>👨⚕️ ${h.doctor || '-'}</span>
|
||||||
|
<span>💊 ${h.medication_count}종</span>
|
||||||
|
<span>💰 ${(h.copayment || 0).toLocaleString()}원</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 약품 테이블
|
||||||
|
if (h.medications && h.medications.length > 0) {
|
||||||
|
document.getElementById('historyMeds').innerHTML = `
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>약품명</th>
|
||||||
|
<th>용량</th>
|
||||||
|
<th>횟수</th>
|
||||||
|
<th>일수</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${h.medications.map(m => `
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="med-name">${m.med_name || m.medication_code}</div>
|
||||||
|
${m.add_info ? `<div style="font-size:0.7rem;color:#94a3b8;">${escapeHtml(m.add_info)}</div>` : ''}
|
||||||
|
</td>
|
||||||
|
<td>${m.dosage || '-'}</td>
|
||||||
|
<td>${m.frequency || '-'}회</td>
|
||||||
|
<td>${m.duration || '-'}일</td>
|
||||||
|
</tr>
|
||||||
|
`).join('')}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
document.getElementById('historyMeds').innerHTML = '<div class="history-empty">약품 정보 없음</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 이전 처방 네비게이션
|
||||||
|
function prevHistory() {
|
||||||
|
if (historyIndex > 0) {
|
||||||
|
historyIndex--;
|
||||||
|
renderHistory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextHistory() {
|
||||||
|
if (historyIndex < historyData.length - 1) {
|
||||||
|
historyIndex++;
|
||||||
|
renderHistory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 상세 초기화
|
// 상세 초기화
|
||||||
function clearDetail() {
|
function clearDetail() {
|
||||||
document.getElementById('detailHeader').style.display = 'none';
|
document.getElementById('detailHeader').style.display = 'none';
|
||||||
document.getElementById('actionBar').style.display = 'none';
|
document.getElementById('actionBar').style.display = 'none';
|
||||||
|
document.getElementById('historySection').style.display = 'none';
|
||||||
document.getElementById('medicationList').innerHTML = `
|
document.getElementById('medicationList').innerHTML = `
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<div class="icon">👈</div>
|
<div class="icon">👈</div>
|
||||||
@ -475,6 +664,9 @@
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
currentPrescriptionId = null;
|
currentPrescriptionId = null;
|
||||||
|
currentPatientCode = null;
|
||||||
|
historyData = [];
|
||||||
|
historyIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 전체 선택 토글
|
// 전체 선택 토글
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user