diff --git a/backend/app.py b/backend/app.py index 0ced1f4..8a3ffbe 100644 --- a/backend/app.py +++ b/backend/app.py @@ -3906,6 +3906,53 @@ def api_products(): return jsonify({'success': False, 'error': str(e)}), 500 +# ==================== 건조시럽 환산계수 API ==================== + +@app.route('/api/drug-info/conversion-factor/') +def api_conversion_factor(sung_code): + """ + 건조시럽 환산계수 조회 API + + PostgreSQL drysyrup 테이블에서 SUNG_CODE로 환산계수 조회 + mL → g 변환에 사용 (예: 120ml * 0.11 = 13.2g) + + Args: + sung_code: 성분코드 (예: "535000ASY") + + Returns: + { + "sung_code": "535000ASY", + "conversion_factor": 0.11, + "ingredient_name": "아목시실린수화물·클라불란산칼륨", + "product_name": "일성오구멘틴듀오시럽 228mg/5ml" + } + + 연결 실패/데이터 없음 시: + {"sung_code": "...", "conversion_factor": null, ...} + """ + try: + result = db_manager.get_conversion_factor(sung_code) + + return jsonify({ + 'success': True, + 'sung_code': sung_code, + 'conversion_factor': result['conversion_factor'], + 'ingredient_name': result['ingredient_name'], + 'product_name': result['product_name'] + }) + + except Exception as e: + logging.error(f"환산계수 조회 오류 (SUNG_CODE={sung_code}): {e}") + # 에러 발생해도 null 반환 (서비스 중단 방지) + return jsonify({ + 'success': True, + 'sung_code': sung_code, + 'conversion_factor': None, + 'ingredient_name': None, + 'product_name': None + }) + + # ==================== 입고이력 API ==================== @app.route('/api/drugs//purchase-history') diff --git a/backend/db/dbsetup.py b/backend/db/dbsetup.py index 66c1415..371a7b2 100644 --- a/backend/db/dbsetup.py +++ b/backend/db/dbsetup.py @@ -2,6 +2,8 @@ PIT3000 Database Setup SQLAlchemy 기반 데이터베이스 연결 및 스키마 정의 Windows/Linux 크로스 플랫폼 지원 + +PostgreSQL 지원 추가: 건조시럽 환산계수 조회 (drysyrup 테이블) """ from sqlalchemy import create_engine, MetaData, text @@ -87,6 +89,9 @@ class DatabaseConfig: # URL 인코딩된 드라이버 DRIVER_ENCODED = urllib.parse.quote_plus(DRIVER) + + # PostgreSQL 연결 정보 (건조시럽 환산계수 DB) + POSTGRES_URL = "postgresql+psycopg2://admin:trajet6640@192.168.0.39:5432/label10" # 데이터베이스별 연결 문자열 (동적 드라이버 사용) @classmethod @@ -135,6 +140,10 @@ class DatabaseManager: # SQLite 연결 추가 self.sqlite_conn = None self.sqlite_db_path = Path(__file__).parent / 'mileage.db' + + # PostgreSQL 연결 (건조시럽 환산계수) + self.postgres_engine = None + self.postgres_session = None def get_engine(self, database='PM_BASE'): """특정 데이터베이스 엔진 반환""" @@ -220,6 +229,127 @@ class DatabaseManager: # 새 세션 생성 return self.get_session(database) + # ───────────────────────────────────────────────────────────── + # PostgreSQL 연결 (건조시럽 환산계수) + # ───────────────────────────────────────────────────────────── + def get_postgres_engine(self): + """ + PostgreSQL 엔진 반환 (건조시럽 환산계수 DB) + + Returns: + Engine 또는 None (연결 실패 시) + """ + if self.postgres_engine is not None: + return self.postgres_engine + + try: + self.postgres_engine = create_engine( + DatabaseConfig.POSTGRES_URL, + pool_size=5, + max_overflow=5, + pool_timeout=30, + pool_recycle=1800, + pool_pre_ping=True, + echo=False + ) + # 연결 테스트 + with self.postgres_engine.connect() as conn: + conn.execute(text("SELECT 1")) + print("[DB Manager] PostgreSQL 연결 성공") + return self.postgres_engine + except Exception as e: + print(f"[DB Manager] PostgreSQL 연결 실패 (무시됨): {e}") + self.postgres_engine = None + return None + + def get_postgres_session(self): + """ + PostgreSQL 세션 반환 (건조시럽 환산계수 조회용) + + Returns: + Session 또는 None (연결 실패 시) + """ + engine = self.get_postgres_engine() + if engine is None: + return None + + if self.postgres_session is None: + try: + Session = sessionmaker(bind=engine) + self.postgres_session = Session() + except Exception as e: + print(f"[DB Manager] PostgreSQL 세션 생성 실패: {e}") + return None + else: + # 세션 상태 체크 + try: + self.postgres_session.execute(text("SELECT 1")) + except Exception as e: + print(f"[DB Manager] PostgreSQL 세션 복구 시도: {e}") + try: + self.postgres_session.rollback() + except: + pass + try: + self.postgres_session.close() + except: + pass + try: + Session = sessionmaker(bind=engine) + self.postgres_session = Session() + except: + self.postgres_session = None + return None + + return self.postgres_session + + def get_conversion_factor(self, sung_code): + """ + 건조시럽 환산계수 조회 + + Args: + sung_code: SUNG_CODE (예: "535000ASY") + + Returns: + dict: { + 'conversion_factor': float 또는 None, + 'ingredient_name': str 또는 None, + 'product_name': str 또는 None + } + """ + result = { + 'conversion_factor': None, + 'ingredient_name': None, + 'product_name': None + } + + session = self.get_postgres_session() + if session is None: + return result + + try: + query = text(""" + SELECT conversion_factor, ingredient_name, product_name + FROM drysyrup + WHERE ingredient_code = :sung_code + LIMIT 1 + """) + row = session.execute(query, {'sung_code': sung_code}).fetchone() + + if row: + result['conversion_factor'] = float(row[0]) if row[0] is not None else None + result['ingredient_name'] = row[1] + result['product_name'] = row[2] + except Exception as e: + print(f"[DB Manager] 환산계수 조회 실패 (SUNG_CODE={sung_code}): {e}") + # 세션 롤백 + try: + session.rollback() + except: + pass + + return result + def get_sqlite_connection(self, new_connection=False): """ SQLite mileage.db 연결 반환 @@ -442,6 +572,20 @@ class DatabaseManager: if self.sqlite_conn: self.sqlite_conn.close() self.sqlite_conn = None + + # PostgreSQL 연결 종료 + if self.postgres_session: + try: + self.postgres_session.close() + except: + pass + self.postgres_session = None + if self.postgres_engine: + try: + self.postgres_engine.dispose() + except: + pass + self.postgres_engine = None # 전역 데이터베이스 매니저 인스턴스 db_manager = DatabaseManager() diff --git a/backend/pmr_api.py b/backend/pmr_api.py index 6a9718f..be06c7d 100644 --- a/backend/pmr_api.py +++ b/backend/pmr_api.py @@ -571,6 +571,7 @@ def preview_label(): - frequency: 복용 횟수 - duration: 복용 일수 - unit: 단위 (정, 캡슐, mL 등) + - sung_code: 성분코드 (환산계수 조회용, 선택) """ try: data = request.get_json() @@ -582,6 +583,17 @@ def preview_label(): frequency = int(data.get('frequency', 0)) duration = int(data.get('duration', 0)) unit = data.get('unit', '정') + sung_code = data.get('sung_code', '') + + # 환산계수 조회 (sung_code가 있는 경우) + conversion_factor = None + if sung_code: + try: + from db.dbsetup import db_manager + cf_result = db_manager.get_conversion_factor(sung_code) + conversion_factor = cf_result.get('conversion_factor') + except Exception as cf_err: + logging.warning(f"환산계수 조회 실패 (무시): {cf_err}") # 라벨 이미지 생성 image = create_label_image( @@ -591,7 +603,8 @@ def preview_label(): dosage=dosage, frequency=frequency, duration=duration, - unit=unit + unit=unit, + conversion_factor=conversion_factor ) # Base64 인코딩 @@ -602,7 +615,8 @@ def preview_label(): return jsonify({ 'success': True, - 'image': f'data:image/png;base64,{img_base64}' + 'image': f'data:image/png;base64,{img_base64}', + 'conversion_factor': conversion_factor }) except Exception as e: @@ -679,9 +693,14 @@ def draw_scissor_border(draw, width, height, edge_size=5, steps=20): draw.line(right_points, fill="black", width=2) -def create_label_image(patient_name, med_name, add_info='', dosage=0, frequency=0, duration=0, unit='정'): +def create_label_image(patient_name, med_name, add_info='', dosage=0, frequency=0, duration=0, unit='정', conversion_factor=None): """ 라벨 이미지 생성 (29mm 용지 기준) - 레거시 디자인 적용 + + Args: + conversion_factor: 건조시럽 환산계수 (mL→g 변환용, 선택) + - 예: 0.11이면 120ml * 0.11 = 13.2g + - 총량 옆에 괄호로 표시: "총120mL (13.2g)/5일분" """ # 약품명 정제 (밀리그램 → mg 등) med_name = normalize_medication_name(med_name) @@ -811,11 +830,21 @@ def create_label_image(patient_name, med_name, add_info='', dosage=0, frequency= y += 5 - # 총량 계산 및 표시 + # 총량 계산 및 표시 (환산계수 반영) if dosage > 0 and frequency > 0 and duration > 0: total = dosage * frequency * duration total_str = str(int(total)) if total == int(total) else f"{total:.2f}".rstrip('0').rstrip('.') - total_text = f"총{total_str}{unit}/{duration}일분" + + # 환산계수가 있으면 변환된 총량도 표시 (예: "총120mL (13.2g)/5일분") + if conversion_factor is not None and conversion_factor > 0: + converted_total = total * conversion_factor + if converted_total == int(converted_total): + converted_str = str(int(converted_total)) + else: + converted_str = f"{converted_total:.2f}".rstrip('0').rstrip('.') + total_text = f"총{total_str}{unit} ({converted_str}g)/{duration}일분" + else: + total_text = f"총{total_str}{unit}/{duration}일분" y = draw_centered(total_text, y, additional_font) y += 5 diff --git a/backend/templates/pmr.html b/backend/templates/pmr.html index 014ebcc..3aa93bb 100644 --- a/backend/templates/pmr.html +++ b/backend/templates/pmr.html @@ -277,6 +277,47 @@ background: rgba(255,255,255,0.35); transform: scale(1.05); } + + /* 키오스크 스타일 전화번호 입력 */ + .phone-input-kiosk { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + margin: 20px 0; + } + .phone-input-kiosk .phone-prefix { + font-size: 28px; + font-weight: 700; + color: #4c1d95; + background: #f3e8ff; + padding: 12px 16px; + border-radius: 10px; + } + .phone-input-kiosk .phone-hyphen { + font-size: 28px; + font-weight: 300; + color: #9ca3af; + } + .phone-input-kiosk input { + width: 80px; + font-size: 28px; + font-weight: 600; + text-align: center; + padding: 12px 8px; + border: 2px solid #e2e8f0; + border-radius: 10px; + transition: all 0.2s; + } + .phone-input-kiosk input:focus { + border-color: #f59e0b; + outline: none; + box-shadow: 0 0 0 3px rgba(245,158,11,0.2); + } + .phone-input-kiosk input::placeholder { + color: #d1d5db; + font-weight: 400; + } .detail-header .cusetc-inline .cusetc-label { font-weight: 600; margin-right: 6px; @@ -1336,17 +1377,22 @@ - +
-
+
-

📞 환자 전화번호

+

📞 전화번호 입력

- -
💡 하이픈(-) 포함해서 입력하세요
+
+ 010 + - + + - + +