완전한 약국 관리 및 사용자-약국 매칭 시스템 구현

🏥 약국 관리 API 구현:
- POST /api/pharmacy - 새 약국 생성 (모든 DB 칼럼 지원)
- PUT /api/pharmacy/<id> - 약국 정보 수정
- DELETE /api/pharmacy/<id>/delete - 약국 삭제
- 약국 관리 페이지 UI 완전 연동

👤 사용자-약국 매칭 시스템:
- POST /api/users/<user>/link-pharmacy - 사용자와 약국 연결
- 실시간 매칭 상태 표시 및 업데이트
- Headscale 사용자와 FARMQ 약국 간 완전한 연결

🔧 핵심 설계 원칙 100% 준수:
- Headscale CLI 기반 제어 (사용자 생성/삭제)
- 이중 사용자 구분 (Headscale ↔ FARMQ 약국)
- 느슨한 결합 (headscale_user_name 매핑)
- 실시간 동기화 (API 호출 즉시 반영)

 전체 시스템 통합 테스트 완료:
- 약국 생성 → 사용자 생성 → 매칭 → 실시간 확인
- DB 칼럼 구조와 완벽 일치
- UI/API 완전 연동

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-11 11:17:13 +09:00
parent fd8c5cbb81
commit e71cdb2cda
3 changed files with 265 additions and 44 deletions

View File

@@ -212,49 +212,213 @@ def create_app(config_name=None):
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/pharmacy/<int:pharmacy_id>/update', methods=['PUT'])
def api_update_pharmacy(pharmacy_id):
"""약국 정보 업데이트 API"""
# 약국 관리 API
@app.route('/api/pharmacy', methods=['POST'])
def api_create_pharmacy():
"""새 약국 생성"""
try:
from utils.database_new import get_farmq_session
from models.farmq_models import PharmacyInfo
data = request.get_json()
session = get_farmq_session()
# 필수 필드 확인
pharmacy_name = data.get('pharmacy_name', '').strip()
if not pharmacy_name:
return jsonify({
'success': False,
'error': '약국명은 필수입니다.'
}), 400
# FARMQ 데이터베이스에 약국 생성
farmq_session = get_farmq_session()
try:
pharmacy = session.query(PharmacyInfo).filter(
new_pharmacy = PharmacyInfo(
pharmacy_name=pharmacy_name,
business_number=data.get('business_number', '').strip(),
manager_name=data.get('manager_name', '').strip(),
phone=data.get('phone', '').strip(),
address=data.get('address', '').strip(),
proxmox_host=data.get('proxmox_host', '').strip(),
headscale_user_name=data.get('headscale_user_name', '').strip(),
status='active'
)
farmq_session.add(new_pharmacy)
farmq_session.commit()
return jsonify({
'success': True,
'message': f'약국 "{pharmacy_name}"가 성공적으로 생성되었습니다.',
'pharmacy': new_pharmacy.to_dict()
})
finally:
farmq_session.close()
except Exception as e:
print(f"❌ 약국 생성 오류: {e}")
return jsonify({
'success': False,
'error': f'서버 오류: {str(e)}'
}), 500
@app.route('/api/pharmacy/<int:pharmacy_id>', methods=['PUT'])
def api_update_pharmacy(pharmacy_id):
"""약국 정보 수정"""
try:
data = request.get_json()
farmq_session = get_farmq_session()
try:
pharmacy = farmq_session.query(PharmacyInfo).filter(
PharmacyInfo.id == pharmacy_id
).first()
if not pharmacy:
return jsonify({'error': '약국을 찾을 수 없습니다.'}), 404
return jsonify({
'success': False,
'error': '약국을 찾을 수 없습니다.'
}), 404
# 업데이트 가능한 필드들
# 필드 업데이트
if 'pharmacy_name' in data:
pharmacy.pharmacy_name = data['pharmacy_name']
pharmacy.pharmacy_name = data['pharmacy_name'].strip()
if 'business_number' in data:
pharmacy.business_number = data['business_number']
pharmacy.business_number = data['business_number'].strip()
if 'manager_name' in data:
pharmacy.manager_name = data['manager_name']
pharmacy.manager_name = data['manager_name'].strip()
if 'phone' in data:
pharmacy.phone = data['phone']
pharmacy.phone = data['phone'].strip()
if 'address' in data:
pharmacy.address = data['address']
pharmacy.address = data['address'].strip()
if 'proxmox_host' in data:
pharmacy.proxmox_host = data['proxmox_host'].strip()
if 'headscale_user_name' in data:
pharmacy.headscale_user_name = data['headscale_user_name'].strip()
pharmacy.updated_at = datetime.now()
session.commit()
farmq_session.commit()
return jsonify({
'message': '약국 정보가 업데이트되었습니다.',
'success': True,
'message': f'약국 "{pharmacy.pharmacy_name}" 정보가 수정되었습니다.',
'pharmacy': pharmacy.to_dict()
})
finally:
session.close()
farmq_session.close()
except Exception as e:
return jsonify({'error': str(e)}), 500
print(f"❌ 약국 수정 오류: {e}")
return jsonify({
'success': False,
'error': f'서버 오류: {str(e)}'
}), 500
@app.route('/api/pharmacy/<int:pharmacy_id>/delete', methods=['DELETE'])
def api_delete_pharmacy(pharmacy_id):
"""약국 삭제"""
try:
farmq_session = get_farmq_session()
try:
pharmacy = farmq_session.query(PharmacyInfo).filter(
PharmacyInfo.id == pharmacy_id
).first()
if not pharmacy:
return jsonify({
'success': False,
'error': '약국을 찾을 수 없습니다.'
}), 404
pharmacy_name = pharmacy.pharmacy_name
farmq_session.delete(pharmacy)
farmq_session.commit()
return jsonify({
'success': True,
'message': f'약국 "{pharmacy_name}"가 삭제되었습니다.'
})
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):
"""사용자와 약국 연결"""
try:
data = request.get_json()
pharmacy_id = data.get('pharmacy_id')
if not pharmacy_id:
return jsonify({
'success': False,
'error': '약국 ID가 필요합니다.'
}), 400
farmq_session = get_farmq_session()
try:
# 약국 존재 확인
pharmacy = farmq_session.query(PharmacyInfo).filter(
PharmacyInfo.id == pharmacy_id
).first()
if not pharmacy:
return jsonify({
'success': False,
'error': '약국을 찾을 수 없습니다.'
}), 404
# 사용자 존재 확인 (Headscale CLI 사용)
result = subprocess.run(
['docker', 'exec', 'headscale', 'headscale', 'users', 'list', '-o', 'json'],
capture_output=True,
text=True,
check=True
)
users_data = json.loads(result.stdout)
user = next((u for u in users_data if u['name'] == user_name), None)
if not user:
return jsonify({
'success': False,
'error': f'사용자 "{user_name}"를 찾을 수 없습니다.'
}), 404
# 약국에 사용자 연결
pharmacy.headscale_user_name = user_name
pharmacy.headscale_user_id = user['id']
pharmacy.updated_at = datetime.now()
farmq_session.commit()
return jsonify({
'success': True,
'message': f'사용자 "{user_name}"가 약국 "{pharmacy.pharmacy_name}"에 연결되었습니다.'
})
finally:
farmq_session.close()
except subprocess.CalledProcessError as e:
error_msg = e.stderr if e.stderr else e.stdout
return jsonify({
'success': False,
'error': f'사용자 확인 실패: {error_msg}'
}), 400
except Exception as e:
print(f"❌ 사용자-약국 연결 오류: {e}")
return jsonify({
'success': False,
'error': f'서버 오류: {str(e)}'
}), 500
# VNC 관리 라우트들
@app.route('/vms')