feat(pmr): 환자 전화번호 표시/수정 기능 추가

- API: 처방 조회 시 CD_PERSON.PHONE 반환
- API: PUT /api/members/{code}/phone - 전화번호 저장
- UI: 나이/성별 옆에 전화번호 뱃지 표시
- UI: 전화번호 없으면 '전화번호 추가' 클릭 가능
- UI: 클릭 시 모달에서 전화번호 입력/저장
This commit is contained in:
thug0bin 2026-03-11 23:42:13 +09:00
parent 4c033b0584
commit e254c5c23d
3 changed files with 172 additions and 9 deletions

View File

@ -5193,6 +5193,42 @@ def api_member_detail(cuscode):
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/members/<cuscode>/phone', methods=['PUT'])
def api_update_phone(cuscode):
"""환자 전화번호 수정 API"""
try:
data = request.get_json() or {}
new_phone = data.get('phone', '').strip()
# 길이 제한 (20자)
if len(new_phone) > 20:
return jsonify({'success': False, 'error': '전화번호는 20자를 초과할 수 없습니다.'}), 400
base_session = db_manager.get_session('PM_BASE')
# PHONE 업데이트
update_query = text("""
UPDATE CD_PERSON
SET PHONE = :phone
WHERE CUSCODE = :cuscode
""")
result = base_session.execute(update_query, {'phone': new_phone, 'cuscode': cuscode})
base_session.commit()
if result.rowcount == 0:
return jsonify({'success': False, 'error': '해당 고객을 찾을 수 없습니다.'}), 404
return jsonify({
'success': True,
'message': '전화번호가 저장되었습니다.',
'phone': new_phone
})
except Exception as e:
logging.error(f"전화번호 수정 오류: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/members/<cuscode>/cusetc', methods=['PUT'])
def api_update_cusetc(cuscode):
"""특이(참고)사항 수정 API"""

View File

@ -431,20 +431,24 @@ def get_prescription_detail(prescription_id):
'name_2': disease_name_2
}
# 환자 특이사항(CUSETC) 조회 - CD_PERSON 테이블
# 환자 특이사항(CUSETC) + 전화번호 조회 - CD_PERSON 테이블
cusetc = ''
phone = ''
cus_code = rx_row.CusCode
if cus_code:
try:
# PM_BASE.dbo.CD_PERSON에서 조회
cursor.execute("""
SELECT CUSETC FROM PM_BASE.dbo.CD_PERSON WHERE CUSCODE = ?
SELECT CUSETC, PHONE, TEL_NO, PHONE2 FROM PM_BASE.dbo.CD_PERSON WHERE CUSCODE = ?
""", (cus_code,))
person_row = cursor.fetchone()
if person_row and person_row.CUSETC:
cusetc = person_row.CUSETC
if person_row:
if person_row.CUSETC:
cusetc = person_row.CUSETC
# 전화번호 (PHONE, TEL_NO, PHONE2 중 하나)
phone = person_row.PHONE or person_row.TEL_NO or person_row.PHONE2 or ''
except Exception as e:
logging.warning(f"특이사항 조회 실패: {e}")
logging.warning(f"환자정보 조회 실패: {e}")
conn.close()
@ -467,7 +471,8 @@ def get_prescription_detail(prescription_id):
'cus_code': rx_row.CusCode, # 호환성
'age': age,
'gender': gender,
'cusetc': cusetc # 특이사항
'cusetc': cusetc, # 특이사항
'phone': phone # 전화번호
},
'disease_info': disease_info,
'medications': medications,

View File

@ -254,6 +254,29 @@
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
/* 전화번호 뱃지 */
.detail-header .phone-badge {
cursor: pointer;
margin-left: 10px;
padding: 3px 10px;
border-radius: 12px;
font-size: 0.85rem;
transition: all 0.2s;
}
.detail-header .phone-badge.has-phone {
background: rgba(255,255,255,0.25);
color: #fff;
}
.detail-header .phone-badge.no-phone {
background: rgba(255,255,255,0.1);
color: rgba(255,255,255,0.7);
border: 1px dashed rgba(255,255,255,0.4);
}
.detail-header .phone-badge:hover {
background: rgba(255,255,255,0.35);
transform: scale(1.05);
}
.detail-header .cusetc-inline .cusetc-label {
font-weight: 600;
margin-right: 6px;
@ -1313,6 +1336,25 @@
</div>
</div>
<!-- 전화번호 모달 -->
<div class="cusetc-modal" id="phoneModal">
<div class="cusetc-modal-content">
<div class="cusetc-modal-header">
<h3>📞 환자 전화번호</h3>
<button class="cusetc-modal-close" onclick="closePhoneModal()">×</button>
</div>
<div class="cusetc-modal-body">
<div class="cusetc-patient-info" id="phonePatientInfo"></div>
<input type="tel" id="phoneInput" placeholder="010-0000-0000" style="width:100%;padding:12px;border:2px solid #e2e8f0;border-radius:8px;font-size:16px;text-align:center;">
<div class="cusetc-hint">💡 하이픈(-) 포함해서 입력하세요</div>
</div>
<div class="cusetc-modal-footer">
<button class="cusetc-btn-cancel" onclick="closePhoneModal()">취소</button>
<button class="cusetc-btn-save" onclick="savePhone()">💾 저장</button>
</div>
</div>
</div>
<!-- OTC 구매 이력 모달 -->
<div class="otc-modal" id="otcModal">
<div class="otc-modal-content">
@ -1501,19 +1543,28 @@
pre_serial: prescriptionId,
cus_code: data.patient.cus_code || data.patient.code,
name: data.patient.name,
age: data.patient.age,
gender: data.patient.gender,
st1: data.disease_info?.code_1 || '',
st1_name: data.disease_info?.name_1 || '',
st2: data.disease_info?.code_2 || '',
st2_name: data.disease_info?.name_2 || '',
medications: data.medications || [],
cusetc: data.patient.cusetc || '' // 특이사항
cusetc: data.patient.cusetc || '', // 특이사항
phone: data.patient.phone || '' // 전화번호
};
// 헤더 업데이트
document.getElementById('detailHeader').style.display = 'block';
document.getElementById('detailName').textContent = data.patient.name || '이름없음';
document.getElementById('detailInfo').textContent =
`${data.patient.age || '-'}세 / ${data.patient.gender || '-'} / ${data.patient.birthdate || '-'}`;
// 나이/성별 + 전화번호 표시
const phone = data.patient.phone || '';
const phoneDisplay = phone
? `<span class="phone-badge has-phone" onclick="event.stopPropagation();openPhoneModal()" title="${phone}">📞 ${phone}</span>`
: `<span class="phone-badge no-phone" onclick="event.stopPropagation();openPhoneModal()">📞 전화번호 추가</span>`;
document.getElementById('detailInfo').innerHTML =
`${data.patient.age || '-'}세 / ${data.patient.gender || '-'} ${phoneDisplay}`;
// 질병 정보 표시 (각각 별도 뱃지)
let diseaseHtml = '';
@ -2065,6 +2116,77 @@
}
}
// ─────────────────────────────────────────────────────────────
// 전화번호 모달 함수들
// ─────────────────────────────────────────────────────────────
function openPhoneModal() {
if (!currentPrescriptionData) {
alert('❌ 먼저 환자를 선택하세요.');
return;
}
const modal = document.getElementById('phoneModal');
const patientInfo = document.getElementById('phonePatientInfo');
const input = document.getElementById('phoneInput');
patientInfo.innerHTML = `
<strong>${currentPrescriptionData.name || '환자'}</strong>
<span style="margin-left: 10px; color: #6b7280;">고객코드: ${currentPrescriptionData.cus_code || '-'}</span>
`;
input.value = currentPrescriptionData.phone || '';
modal.style.display = 'flex';
input.focus();
}
function closePhoneModal() {
document.getElementById('phoneModal').style.display = 'none';
}
async function savePhone() {
if (!currentPrescriptionData || !currentPrescriptionData.cus_code) {
alert('❌ 환자 정보가 없습니다.');
return;
}
const input = document.getElementById('phoneInput');
const newPhone = input.value.trim();
const cusCode = currentPrescriptionData.cus_code;
try {
const res = await fetch(`/api/members/${cusCode}/phone`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone: newPhone })
});
const data = await res.json();
if (data.success) {
currentPrescriptionData.phone = newPhone;
updatePhoneBadge(newPhone);
closePhoneModal();
showPaaiToast(currentPrescriptionData.name, '전화번호가 저장되었습니다.', 'completed');
} else {
alert('❌ ' + (data.error || '저장 실패'));
}
} catch (err) {
alert('❌ 오류: ' + err.message);
}
}
function updatePhoneBadge(phone) {
const detailInfo = document.getElementById('detailInfo');
if (!detailInfo || !currentPrescriptionData) return;
const phoneDisplay = phone
? `<span class="phone-badge has-phone" onclick="event.stopPropagation();openPhoneModal()" title="${phone}">📞 ${phone}</span>`
: `<span class="phone-badge no-phone" onclick="event.stopPropagation();openPhoneModal()">📞 전화번호 추가</span>`;
detailInfo.innerHTML =
`${currentPrescriptionData.age || '-'}세 / ${currentPrescriptionData.gender || '-'} ${phoneDisplay}`;
}
// ─────────────────────────────────────────────────────────────
// PAAI (Pharmacist Assistant AI) 함수들 - 비동기 토스트 방식
// ─────────────────────────────────────────────────────────────