diff --git a/farmq-admin/app.py b/farmq-admin/app.py index b4d9c91..fb682e1 100644 --- a/farmq-admin/app.py +++ b/farmq-admin/app.py @@ -212,49 +212,213 @@ def create_app(config_name=None): except Exception as e: return jsonify({'error': str(e)}), 500 - @app.route('/api/pharmacy//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/', 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//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//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') diff --git a/farmq-admin/templates/pharmacy/list.html b/farmq-admin/templates/pharmacy/list.html index 90a9f61..afe0672 100644 --- a/farmq-admin/templates/pharmacy/list.html +++ b/farmq-admin/templates/pharmacy/list.html @@ -177,8 +177,8 @@
- - + +
@@ -234,7 +234,7 @@ function showEditModal(pharmacyId) { document.getElementById('phone').value = pharmacy.phone || ''; document.getElementById('address').value = pharmacy.address || ''; document.getElementById('proxmox_host').value = pharmacy.proxmox_host || ''; - document.getElementById('user_id').value = pharmacy.user_id || ''; + document.getElementById('headscale_user_name').value = pharmacy.headscale_user_name || ''; // 수정 모드임을 표시하기 위해 pharmacy ID를 form에 저장 document.getElementById('pharmacyForm').dataset.pharmacyId = pharmacyId; @@ -264,12 +264,12 @@ document.getElementById('pharmacyForm').addEventListener('submit', function(e) { phone: document.getElementById('phone').value, address: document.getElementById('address').value, proxmox_host: document.getElementById('proxmox_host').value, - user_id: document.getElementById('user_id').value + headscale_user_name: document.getElementById('headscale_user_name').value }; if (mode === 'edit' && pharmacyId) { // 수정 모드: PUT 요청 - fetch(`/api/pharmacy/${pharmacyId}/update`, { + fetch(`/api/pharmacy/${pharmacyId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', @@ -278,12 +278,12 @@ document.getElementById('pharmacyForm').addEventListener('submit', function(e) { }) .then(response => response.json()) .then(result => { - if (result.error) { - showToast(result.error, 'error'); - } else { - showToast('약국 정보가 수정되었습니다.', 'success'); + if (result.success) { + showToast(result.message, 'success'); pharmacyModal.hide(); setTimeout(() => location.reload(), 1000); + } else { + showToast(result.error, 'error'); } }) .catch(error => { @@ -291,9 +291,28 @@ document.getElementById('pharmacyForm').addEventListener('submit', function(e) { showToast('약국 정보 수정에 실패했습니다.', 'error'); }); } else { - // 새 등록 모드: POST 요청 (향후 구현) - showToast('새 약국 등록 기능은 아직 구현 중입니다.', 'warning'); - pharmacyModal.hide(); + // 새 등록 모드: POST 요청 + fetch('/api/pharmacy', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data) + }) + .then(response => response.json()) + .then(result => { + if (result.success) { + showToast(result.message, 'success'); + pharmacyModal.hide(); + setTimeout(() => location.reload(), 1000); + } else { + showToast(result.error, 'error'); + } + }) + .catch(error => { + console.error('약국 생성 실패:', error); + showToast('약국 생성에 실패했습니다.', 'error'); + }); } }); diff --git a/farmq-admin/templates/users/list.html b/farmq-admin/templates/users/list.html index cc48677..3a4530e 100644 --- a/farmq-admin/templates/users/list.html +++ b/farmq-admin/templates/users/list.html @@ -214,16 +214,26 @@ function loadUsers() { // 약국 목록 로드 function loadPharmacies() { - fetch('/api/pharmacy/1') // 임시로 약국 API 사용 - .then(response => response.json()) - .catch(error => { - console.log('약국 목록 로드 중 오류 (정상적 동작)'); - // 약국 목록 API가 없으므로 임시 데이터 사용 - currentPharmacies = [ - {id: 1, pharmacy_name: '제1약국', manager_name: '김약사'}, - {id: 2, pharmacy_name: '제2약국', manager_name: '이약사'} - ]; - }); + // FARMQ 약국 데이터 직접 조회 + farmq_session = get_farmq_session() + try { + pharmacies = farmq_session.query(PharmacyInfo).filter( + PharmacyInfo.status == 'active' + ).all() + + currentPharmacies = pharmacies.map(p => ({ + id: p.id, + pharmacy_name: p.pharmacy_name, + manager_name: p.manager_name + })) + } catch (error) { + console.log('약국 목록 로드 중 오류:', error); + // 임시 데이터 사용 + currentPharmacies = [ + {id: 1, pharmacy_name: '양구청춘약국', manager_name: '김영빈'}, + {id: 2, pharmacy_name: '성진약국', manager_name: '박성진'} + ]; + } } // 사용자 테이블 렌더링 @@ -422,7 +432,7 @@ function showLinkPharmacyModal(userName) { modal.show(); } -// 약국 연결 (임시 기능) +// 약국 연결 function linkPharmacy() { const pharmacyId = document.getElementById('pharmacySelect').value; if (!pharmacyId) { @@ -430,8 +440,36 @@ function linkPharmacy() { return; } - showToast('약국 연결 기능은 개발 중입니다.', 'info'); - bootstrap.Modal.getInstance(document.getElementById('linkPharmacyModal')).hide(); + if (!selectedUserId) { + showToast('사용자 정보가 없습니다.', 'error'); + return; + } + + showToast('약국 연결 중...', 'info'); + + fetch(`/api/users/${selectedUserId}/link-pharmacy`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + pharmacy_id: parseInt(pharmacyId) + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showToast(data.message, 'success'); + bootstrap.Modal.getInstance(document.getElementById('linkPharmacyModal')).hide(); + setTimeout(loadUsers, 1000); + } else { + showToast('약국 연결 실패: ' + data.error, 'danger'); + } + }) + .catch(error => { + console.error('약국 연결 오류:', error); + showToast('약국 연결 중 오류가 발생했습니다.', 'danger'); + }); } // 목록 새로고침