feat: 환자 처방 내역 조회 기능 추가
- 환자 목록에 처방 횟수 표시 (배지) - 환자별 처방 내역 조회 버튼 추가 - 환자 처방 내역 모달 추가 (통계 + 상세 내역) - 총 처방 횟수, 최근 방문일, 총 제수, 총 처방비 - 전체 처방 내역 테이블 (조제일, 처방명, 상태 등) - 각 처방의 상세 보기 기능 연동 - 환자 개별 조회 API 엔드포인트 추가 (GET /api/patients/<id>) - 환자 편집 버튼 UI 추가 (기능 준비) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
724af5000a
commit
45672a125f
19
app.py
19
app.py
@ -83,6 +83,25 @@ def get_patients():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/api/patients/<int:patient_id>', methods=['GET'])
|
||||||
|
def get_patient(patient_id):
|
||||||
|
"""환자 개별 조회"""
|
||||||
|
try:
|
||||||
|
with get_db() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT patient_id, name, phone, jumin_no, gender, birth_date, address, notes
|
||||||
|
FROM patients
|
||||||
|
WHERE patient_id = ? AND is_active = 1
|
||||||
|
""", (patient_id,))
|
||||||
|
patient_row = cursor.fetchone()
|
||||||
|
if patient_row:
|
||||||
|
return jsonify({'success': True, 'data': dict(patient_row)})
|
||||||
|
else:
|
||||||
|
return jsonify({'success': False, 'error': '환자를 찾을 수 없습니다'}), 404
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
@app.route('/api/patients', methods=['POST'])
|
@app.route('/api/patients', methods=['POST'])
|
||||||
def create_patient():
|
def create_patient():
|
||||||
"""새 환자 등록"""
|
"""새 환자 등록"""
|
||||||
|
|||||||
142
static/app.js
142
static/app.js
@ -122,22 +122,53 @@ $(document).ready(function() {
|
|||||||
const tbody = $('#patientsList');
|
const tbody = $('#patientsList');
|
||||||
tbody.empty();
|
tbody.empty();
|
||||||
|
|
||||||
|
// 각 환자의 처방 횟수를 가져오기 위해 처방 데이터도 로드
|
||||||
|
$.get('/api/compounds', function(compoundsResponse) {
|
||||||
|
const compounds = compoundsResponse.success ? compoundsResponse.data : [];
|
||||||
|
|
||||||
|
// 환자별 처방 횟수 계산
|
||||||
|
const compoundCounts = {};
|
||||||
|
compounds.forEach(compound => {
|
||||||
|
if (compound.patient_id) {
|
||||||
|
compoundCounts[compound.patient_id] = (compoundCounts[compound.patient_id] || 0) + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
response.data.forEach(patient => {
|
response.data.forEach(patient => {
|
||||||
|
const compoundCount = compoundCounts[patient.patient_id] || 0;
|
||||||
|
|
||||||
tbody.append(`
|
tbody.append(`
|
||||||
<tr>
|
<tr>
|
||||||
<td>${patient.name}</td>
|
<td><strong>${patient.name}</strong></td>
|
||||||
<td>${patient.phone}</td>
|
<td>${patient.phone}</td>
|
||||||
<td>${patient.gender === 'M' ? '남' : patient.gender === 'F' ? '여' : '-'}</td>
|
<td>${patient.gender === 'M' ? '남' : patient.gender === 'F' ? '여' : '-'}</td>
|
||||||
<td>${patient.birth_date || '-'}</td>
|
<td>${patient.birth_date || '-'}</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-primary">${compoundCount}회</span>
|
||||||
|
</td>
|
||||||
<td>${patient.notes || '-'}</td>
|
<td>${patient.notes || '-'}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-sm btn-outline-primary">
|
<button class="btn btn-sm btn-outline-info view-patient-compounds"
|
||||||
|
data-id="${patient.patient_id}"
|
||||||
|
data-name="${patient.name}">
|
||||||
|
<i class="bi bi-file-medical"></i> 처방내역
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-primary edit-patient"
|
||||||
|
data-id="${patient.patient_id}">
|
||||||
<i class="bi bi-pencil"></i>
|
<i class="bi bi-pencil"></i>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 처방내역 버튼 이벤트
|
||||||
|
$('.view-patient-compounds').on('click', function() {
|
||||||
|
const patientId = $(this).data('id');
|
||||||
|
const patientName = $(this).data('name');
|
||||||
|
viewPatientCompounds(patientId, patientName);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -173,6 +204,113 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 환자 처방 내역 조회
|
||||||
|
function viewPatientCompounds(patientId, patientName) {
|
||||||
|
// 환자 정보 가져오기
|
||||||
|
$.get(`/api/patients/${patientId}`, function(patientResponse) {
|
||||||
|
if (patientResponse.success) {
|
||||||
|
const patient = patientResponse.data;
|
||||||
|
|
||||||
|
// 환자 기본 정보 표시
|
||||||
|
$('#patientCompoundsName').text(patient.name);
|
||||||
|
$('#patientInfoName').text(patient.name);
|
||||||
|
$('#patientInfoPhone').text(patient.phone || '-');
|
||||||
|
$('#patientInfoGender').text(patient.gender === 'M' ? '남성' : patient.gender === 'F' ? '여성' : '-');
|
||||||
|
$('#patientInfoBirth').text(patient.birth_date || '-');
|
||||||
|
|
||||||
|
// 환자의 처방 내역 가져오기
|
||||||
|
$.get(`/api/patients/${patientId}/compounds`, function(compoundsResponse) {
|
||||||
|
if (compoundsResponse.success) {
|
||||||
|
const compounds = compoundsResponse.data;
|
||||||
|
|
||||||
|
// 통계 계산
|
||||||
|
const totalCompounds = compounds.length;
|
||||||
|
let totalJe = 0;
|
||||||
|
let totalAmount = 0;
|
||||||
|
let lastVisit = '-';
|
||||||
|
|
||||||
|
if (compounds.length > 0) {
|
||||||
|
compounds.forEach(c => {
|
||||||
|
totalJe += c.je_count || 0;
|
||||||
|
totalAmount += c.sell_price_total || 0;
|
||||||
|
});
|
||||||
|
lastVisit = compounds[0].compound_date || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 통계 표시
|
||||||
|
$('#patientTotalCompounds').text(totalCompounds + '회');
|
||||||
|
$('#patientLastVisit').text(lastVisit);
|
||||||
|
$('#patientTotalJe').text(totalJe + '제');
|
||||||
|
$('#patientTotalAmount').text(formatCurrency(totalAmount));
|
||||||
|
|
||||||
|
// 처방 내역 테이블 표시
|
||||||
|
const tbody = $('#patientCompoundsList');
|
||||||
|
tbody.empty();
|
||||||
|
|
||||||
|
if (compounds.length === 0) {
|
||||||
|
tbody.append(`
|
||||||
|
<tr>
|
||||||
|
<td colspan="10" class="text-center text-muted">처방 내역이 없습니다.</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
} else {
|
||||||
|
compounds.forEach(compound => {
|
||||||
|
// 상태 뱃지
|
||||||
|
let statusBadge = '';
|
||||||
|
switch(compound.status) {
|
||||||
|
case 'PREPARED':
|
||||||
|
statusBadge = '<span class="badge bg-success">조제완료</span>';
|
||||||
|
break;
|
||||||
|
case 'DISPENSED':
|
||||||
|
statusBadge = '<span class="badge bg-primary">출고완료</span>';
|
||||||
|
break;
|
||||||
|
case 'CANCELLED':
|
||||||
|
statusBadge = '<span class="badge bg-danger">취소</span>';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
statusBadge = '<span class="badge bg-secondary">대기</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody.append(`
|
||||||
|
<tr>
|
||||||
|
<td>${compound.compound_date || '-'}</td>
|
||||||
|
<td>${compound.formula_name || '직접조제'}</td>
|
||||||
|
<td>${compound.je_count || 0}</td>
|
||||||
|
<td>${compound.cheop_total || 0}</td>
|
||||||
|
<td>${compound.pouch_total || 0}</td>
|
||||||
|
<td>${formatCurrency(compound.cost_total || 0)}</td>
|
||||||
|
<td>${formatCurrency(compound.sell_price_total || 0)}</td>
|
||||||
|
<td>${statusBadge}</td>
|
||||||
|
<td>${compound.prescription_no || '-'}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-outline-info view-compound-detail"
|
||||||
|
data-id="${compound.compound_id}">
|
||||||
|
<i class="bi bi-eye"></i> 상세
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 상세보기 버튼 이벤트
|
||||||
|
$('.view-compound-detail').on('click', function() {
|
||||||
|
const compoundId = $(this).data('id');
|
||||||
|
viewCompoundDetail(compoundId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 모달 표시
|
||||||
|
$('#patientCompoundsModal').modal('show');
|
||||||
|
}
|
||||||
|
}).fail(function() {
|
||||||
|
alert('처방 내역을 불러오는데 실패했습니다.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).fail(function() {
|
||||||
|
alert('환자 정보를 불러오는데 실패했습니다.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 처방 목록 로드
|
// 처방 목록 로드
|
||||||
function loadFormulas() {
|
function loadFormulas() {
|
||||||
$.get('/api/formulas', function(response) {
|
$.get('/api/formulas', function(response) {
|
||||||
|
|||||||
@ -205,8 +205,9 @@
|
|||||||
<th>전화번호</th>
|
<th>전화번호</th>
|
||||||
<th>성별</th>
|
<th>성별</th>
|
||||||
<th>생년월일</th>
|
<th>생년월일</th>
|
||||||
|
<th width="80">처방 횟수</th>
|
||||||
<th>메모</th>
|
<th>메모</th>
|
||||||
<th>작업</th>
|
<th width="180">작업</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="patientsList">
|
<tbody id="patientsList">
|
||||||
@ -1024,6 +1025,97 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 환자 처방 내역 모달 -->
|
||||||
|
<div class="modal fade" id="patientCompoundsModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-xl">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-primary text-white">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
<i class="bi bi-person-circle"></i>
|
||||||
|
<span id="patientCompoundsName">환자명</span>의 처방 내역
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- 환자 기본 정보 -->
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<strong>환자명:</strong> <span id="patientInfoName"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<strong>전화번호:</strong> <span id="patientInfoPhone"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<strong>성별:</strong> <span id="patientInfoGender"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<strong>생년월일:</strong> <span id="patientInfoBirth"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 처방 통계 -->
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="stat-card bg-primary text-white">
|
||||||
|
<h6>총 처방 횟수</h6>
|
||||||
|
<h4 id="patientTotalCompounds">0회</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="stat-card bg-success text-white">
|
||||||
|
<h6>최근 방문</h6>
|
||||||
|
<h4 id="patientLastVisit">-</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="stat-card bg-info text-white">
|
||||||
|
<h6>총 제수</h6>
|
||||||
|
<h4 id="patientTotalJe">0제</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="stat-card bg-warning text-white">
|
||||||
|
<h6>총 처방비</h6>
|
||||||
|
<h4 id="patientTotalAmount">₩0</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 처방 내역 테이블 -->
|
||||||
|
<h6><i class="bi bi-list-ul"></i> 처방 내역</h6>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm table-hover">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th width="100">조제일</th>
|
||||||
|
<th>처방명</th>
|
||||||
|
<th width="60">제수</th>
|
||||||
|
<th width="60">첩수</th>
|
||||||
|
<th width="80">파우치</th>
|
||||||
|
<th width="100">원가</th>
|
||||||
|
<th width="100">판매가</th>
|
||||||
|
<th width="80">상태</th>
|
||||||
|
<th width="100">처방전번호</th>
|
||||||
|
<th width="100">작업</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="patientCompoundsList">
|
||||||
|
<!-- Dynamic content -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">닫기</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Formula Modal -->
|
<!-- Formula Modal -->
|
||||||
<div class="modal fade" id="formulaModal" tabindex="-1">
|
<div class="modal fade" id="formulaModal" tabindex="-1">
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user