feat(pmr): 환자 전화번호 표시/수정 기능 추가
- API: 처방 조회 시 CD_PERSON.PHONE 반환
- API: PUT /api/members/{code}/phone - 전화번호 저장
- UI: 나이/성별 옆에 전화번호 뱃지 표시
- UI: 전화번호 없으면 '전화번호 추가' 클릭 가능
- UI: 클릭 시 모달에서 전화번호 입력/저장
This commit is contained in:
parent
4c033b0584
commit
e254c5c23d
@ -5193,6 +5193,42 @@ def api_member_detail(cuscode):
|
|||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
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'])
|
@app.route('/api/members/<cuscode>/cusetc', methods=['PUT'])
|
||||||
def api_update_cusetc(cuscode):
|
def api_update_cusetc(cuscode):
|
||||||
"""특이(참고)사항 수정 API"""
|
"""특이(참고)사항 수정 API"""
|
||||||
|
|||||||
@ -431,20 +431,24 @@ def get_prescription_detail(prescription_id):
|
|||||||
'name_2': disease_name_2
|
'name_2': disease_name_2
|
||||||
}
|
}
|
||||||
|
|
||||||
# 환자 특이사항(CUSETC) 조회 - CD_PERSON 테이블
|
# 환자 특이사항(CUSETC) + 전화번호 조회 - CD_PERSON 테이블
|
||||||
cusetc = ''
|
cusetc = ''
|
||||||
|
phone = ''
|
||||||
cus_code = rx_row.CusCode
|
cus_code = rx_row.CusCode
|
||||||
if cus_code:
|
if cus_code:
|
||||||
try:
|
try:
|
||||||
# PM_BASE.dbo.CD_PERSON에서 조회
|
# PM_BASE.dbo.CD_PERSON에서 조회
|
||||||
cursor.execute("""
|
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,))
|
""", (cus_code,))
|
||||||
person_row = cursor.fetchone()
|
person_row = cursor.fetchone()
|
||||||
if person_row and person_row.CUSETC:
|
if person_row:
|
||||||
cusetc = person_row.CUSETC
|
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:
|
except Exception as e:
|
||||||
logging.warning(f"특이사항 조회 실패: {e}")
|
logging.warning(f"환자정보 조회 실패: {e}")
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@ -467,7 +471,8 @@ def get_prescription_detail(prescription_id):
|
|||||||
'cus_code': rx_row.CusCode, # 호환성
|
'cus_code': rx_row.CusCode, # 호환성
|
||||||
'age': age,
|
'age': age,
|
||||||
'gender': gender,
|
'gender': gender,
|
||||||
'cusetc': cusetc # 특이사항
|
'cusetc': cusetc, # 특이사항
|
||||||
|
'phone': phone # 전화번호
|
||||||
},
|
},
|
||||||
'disease_info': disease_info,
|
'disease_info': disease_info,
|
||||||
'medications': medications,
|
'medications': medications,
|
||||||
|
|||||||
@ -254,6 +254,29 @@
|
|||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
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 {
|
.detail-header .cusetc-inline .cusetc-label {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
@ -1313,6 +1336,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 구매 이력 모달 -->
|
<!-- OTC 구매 이력 모달 -->
|
||||||
<div class="otc-modal" id="otcModal">
|
<div class="otc-modal" id="otcModal">
|
||||||
<div class="otc-modal-content">
|
<div class="otc-modal-content">
|
||||||
@ -1501,19 +1543,28 @@
|
|||||||
pre_serial: prescriptionId,
|
pre_serial: prescriptionId,
|
||||||
cus_code: data.patient.cus_code || data.patient.code,
|
cus_code: data.patient.cus_code || data.patient.code,
|
||||||
name: data.patient.name,
|
name: data.patient.name,
|
||||||
|
age: data.patient.age,
|
||||||
|
gender: data.patient.gender,
|
||||||
st1: data.disease_info?.code_1 || '',
|
st1: data.disease_info?.code_1 || '',
|
||||||
st1_name: data.disease_info?.name_1 || '',
|
st1_name: data.disease_info?.name_1 || '',
|
||||||
st2: data.disease_info?.code_2 || '',
|
st2: data.disease_info?.code_2 || '',
|
||||||
st2_name: data.disease_info?.name_2 || '',
|
st2_name: data.disease_info?.name_2 || '',
|
||||||
medications: data.medications || [],
|
medications: data.medications || [],
|
||||||
cusetc: data.patient.cusetc || '' // 특이사항
|
cusetc: data.patient.cusetc || '', // 특이사항
|
||||||
|
phone: data.patient.phone || '' // 전화번호
|
||||||
};
|
};
|
||||||
|
|
||||||
// 헤더 업데이트
|
// 헤더 업데이트
|
||||||
document.getElementById('detailHeader').style.display = 'block';
|
document.getElementById('detailHeader').style.display = 'block';
|
||||||
document.getElementById('detailName').textContent = data.patient.name || '이름없음';
|
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 = '';
|
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) 함수들 - 비동기 토스트 방식
|
// PAAI (Pharmacist Assistant AI) 함수들 - 비동기 토스트 방식
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user