사용자-약국 매핑 시스템 개선 및 UI 업데이트
- 자동 매핑 버그 수정: 이름만으로 자동 연결되던 문제 해결 - 매핑되지 않은 약국 목록 API 추가 (/api/pharmacies/available) - 사용자 연결 드롭다운에서 매핑 가능한 약국만 표시하도록 개선 - 기존 잘못된 매핑 초기화하여 명시적 링크만 허용 - UI 텍스트 업데이트: "Headscale 사용자 목록" → "PQON 사용자 목록" - UI 텍스트 업데이트: "Headscale 네트워크 사용자" → "PharmQ-ON 사용자" - 사이드 메뉴 링크 변경: "Headplane UI" → "Medivault" (https://medivault.co.kr/) - SQLAlchemy or_ import 추가하여 복합 조건 쿼리 지원 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
e71cdb2cda
commit
56b72629f9
@ -17,6 +17,7 @@ from utils.database_new import (
|
||||
sync_machines_from_headscale, sync_users_from_headscale
|
||||
)
|
||||
from models.farmq_models import PharmacyInfo
|
||||
from sqlalchemy import or_
|
||||
import subprocess
|
||||
from utils.proxmox_client import ProxmoxClient
|
||||
|
||||
@ -349,6 +350,46 @@ def create_app(config_name=None):
|
||||
'error': f'서버 오류: {str(e)}'
|
||||
}), 500
|
||||
|
||||
@app.route('/api/pharmacies/available', methods=['GET'])
|
||||
def api_get_available_pharmacies():
|
||||
"""매핑되지 않은 약국 목록 가져오기"""
|
||||
try:
|
||||
farmq_session = get_farmq_session()
|
||||
try:
|
||||
# headscale_user_name이 NULL이거나 빈 문자열인 약국들만 가져오기
|
||||
pharmacies = farmq_session.query(PharmacyInfo).filter(
|
||||
or_(
|
||||
PharmacyInfo.headscale_user_name.is_(None),
|
||||
PharmacyInfo.headscale_user_name == ''
|
||||
)
|
||||
).all()
|
||||
|
||||
pharmacy_list = []
|
||||
for pharmacy in pharmacies:
|
||||
pharmacy_list.append({
|
||||
'id': pharmacy.id,
|
||||
'pharmacy_name': pharmacy.pharmacy_name,
|
||||
'manager_name': pharmacy.manager_name or '미등록',
|
||||
'business_number': pharmacy.business_number,
|
||||
'address': pharmacy.address
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'pharmacies': pharmacy_list,
|
||||
'count': len(pharmacy_list)
|
||||
})
|
||||
|
||||
finally:
|
||||
farmq_session.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 매핑 가능한 약국 목록 조회 오류: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'서버 오류: {str(e)}'
|
||||
}), 500
|
||||
|
||||
@app.route('/api/users/<user_name>/link-pharmacy', methods=['POST'])
|
||||
def api_link_user_pharmacy(user_name):
|
||||
"""사용자와 약국 연결"""
|
||||
@ -624,17 +665,21 @@ def create_app(config_name=None):
|
||||
|
||||
users_data = json.loads(result.stdout)
|
||||
|
||||
# FARMQ 약국 정보와 매칭
|
||||
# FARMQ 약국 정보와 매칭 (명시적으로 매핑된 것만)
|
||||
farmq_session = get_farmq_session()
|
||||
try:
|
||||
pharmacies = farmq_session.query(PharmacyInfo).all()
|
||||
pharmacy_map = {p.headscale_user_name: p for p in pharmacies if p.headscale_user_name}
|
||||
# 명시적으로 headscale_user_name이 설정되고 해당 사용자가 실제로 존재하는 경우만 매핑
|
||||
pharmacy_map = {}
|
||||
for p in pharmacies:
|
||||
if p.headscale_user_name and p.headscale_user_name.strip():
|
||||
pharmacy_map[p.headscale_user_name] = p
|
||||
|
||||
# 사용자별 노드 수 조회
|
||||
for user in users_data:
|
||||
user_name = user.get('name', '')
|
||||
|
||||
# 약국 정보 매칭
|
||||
# 약국 정보 매칭 - 명시적으로 연결된 것만
|
||||
pharmacy = pharmacy_map.get(user_name)
|
||||
user['pharmacy'] = {
|
||||
'id': pharmacy.id,
|
||||
|
||||
@ -214,8 +214,8 @@
|
||||
<!-- 빠른 링크 -->
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="http://localhost:3000/admin/" target="_blank">
|
||||
<i class="fas fa-external-link-alt"></i> Headplane UI
|
||||
<a class="nav-link" href="https://medivault.co.kr/" target="_blank">
|
||||
<i class="fas fa-external-link-alt"></i> Medivault
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
<i class="fas fa-users text-primary"></i>
|
||||
사용자 관리
|
||||
</h1>
|
||||
<p class="text-muted">Headscale 네트워크 사용자 및 약국 매칭 관리</p>
|
||||
<p class="text-muted">PharmQ-ON 사용자 및 약국 매칭 관리</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-outline-secondary" onclick="refreshUserList()">
|
||||
@ -87,7 +87,7 @@
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-list"></i> Headscale 사용자 목록
|
||||
<i class="fas fa-list"></i> PQON 사용자 목록
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body" id="usersTableContainer">
|
||||
@ -420,13 +420,33 @@ function showLinkPharmacyModal(userName) {
|
||||
document.getElementById('linkUserName').textContent = userName;
|
||||
selectedUserId = userName;
|
||||
|
||||
// 약국 목록을 셀렉트 박스에 추가
|
||||
// 매핑 가능한 약국 목록을 API에서 가져오기
|
||||
const select = document.getElementById('pharmacySelect');
|
||||
select.innerHTML = '<option value="">약국을 선택하세요</option>';
|
||||
select.innerHTML = '<option value="">로딩 중...</option>';
|
||||
|
||||
currentPharmacies.forEach(pharmacy => {
|
||||
select.innerHTML += `<option value="${pharmacy.id}">${pharmacy.pharmacy_name} (${pharmacy.manager_name})</option>`;
|
||||
});
|
||||
fetch('/api/pharmacies/available')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
select.innerHTML = '<option value="">약국을 선택하세요</option>';
|
||||
|
||||
if (data.pharmacies.length === 0) {
|
||||
select.innerHTML = '<option value="">매핑 가능한 약국이 없습니다</option>';
|
||||
} else {
|
||||
data.pharmacies.forEach(pharmacy => {
|
||||
select.innerHTML += `<option value="${pharmacy.id}">${pharmacy.pharmacy_name} (${pharmacy.manager_name})</option>`;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
select.innerHTML = '<option value="">약국 목록 로드 실패</option>';
|
||||
showToast('약국 목록을 불러오는데 실패했습니다.', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('약국 목록 로드 오류:', error);
|
||||
select.innerHTML = '<option value="">약국 목록 로드 실패</option>';
|
||||
showToast('약국 목록을 불러오는데 실패했습니다.', 'error');
|
||||
});
|
||||
|
||||
const modal = new bootstrap.Modal(document.getElementById('linkPharmacyModal'));
|
||||
modal.show();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user