diff --git a/backend/app.py b/backend/app.py index ed3fa26..0443ae1 100644 --- a/backend/app.py +++ b/backend/app.py @@ -50,6 +50,12 @@ app.config['SESSION_COOKIE_SECURE'] = not app.debug # HTTPS 전용 (로컬 개 app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # QR 스캔 시 쿠키 전송 허용 app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=90) # 3개월 유지 +# ───────────────────────────────────────────────────────────── +# Blueprint 등록 +# ───────────────────────────────────────────────────────────── +from pmr_api import pmr_bp +app.register_blueprint(pmr_bp) + # 데이터베이스 매니저 db_manager = DatabaseManager() diff --git a/backend/pmr_api.py b/backend/pmr_api.py new file mode 100644 index 0000000..4dbbe37 --- /dev/null +++ b/backend/pmr_api.py @@ -0,0 +1,322 @@ +# pmr_api.py - 조제관리(PMR) Blueprint API +# PharmaIT3000 MSSQL 연동 (192.168.0.4) + +from flask import Blueprint, jsonify, request, render_template +import pyodbc +from datetime import datetime, date +import logging + +pmr_bp = Blueprint('pmr', __name__, url_prefix='/pmr') + +# ───────────────────────────────────────────────────────────── +# MSSQL 연결 설정 (PharmaIT3000 - 192.168.0.4) +# ───────────────────────────────────────────────────────────── +MSSQL_CONFIG = { + 'server': '192.168.0.4\\PM2014', + 'username': 'sa', + 'password': 'tmddls214!%(', + 'driver': 'ODBC Driver 17 for SQL Server' +} + +def get_mssql_connection(database='PM_PRES'): + """MSSQL 연결 획득""" + conn_str = ( + f"DRIVER={{{MSSQL_CONFIG['driver']}}};" + f"SERVER={MSSQL_CONFIG['server']};" + f"DATABASE={database};" + f"UID={MSSQL_CONFIG['username']};" + f"PWD={MSSQL_CONFIG['password']};" + "TrustServerCertificate=yes;" + "Connection Timeout=10" + ) + return pyodbc.connect(conn_str, timeout=10) + + +# ───────────────────────────────────────────────────────────── +# 조제관리 페이지 +# ───────────────────────────────────────────────────────────── +@pmr_bp.route('/') +def pmr_index(): + """조제관리 메인 페이지""" + return render_template('pmr.html') + + +# ───────────────────────────────────────────────────────────── +# API: 날짜별 처방전 목록 +# ───────────────────────────────────────────────────────────── +@pmr_bp.route('/api/prescriptions', methods=['GET']) +def get_prescriptions_by_date(): + """ + 날짜별 처방전 목록 조회 + Query Params: + - date: YYYY-MM-DD (기본값: 오늘) + """ + try: + date_str = request.args.get('date', date.today().strftime('%Y-%m-%d')) + # YYYYMMDD 형식으로 변환 + date_yyyymmdd = date_str.replace('-', '') + + conn = get_mssql_connection('PM_PRES') + cursor = conn.cursor() + + cursor.execute(""" + SELECT + PreSerial, + Day_Serial, + PassDay, + Paname, + PaNum, + CusCode, + InsName, + Drname, + PresTime, + PreGubun, + PRICE_T, + PRICE_P, + PRICE_C + FROM PS_MAIN + WHERE PassDay = ? + ORDER BY Day_Serial ASC + """, (date_yyyymmdd,)) + + prescriptions = [] + for row in cursor.fetchall(): + # 주민번호에서 나이/성별 추출 + panum = row.PaNum or '' + age = None + gender = None + if len(panum) >= 7: + try: + birth_year = int(panum[:2]) + gender_code = panum[6] if len(panum) > 6 else '' + + # 성별 및 세기 판단 + if gender_code in ['1', '2', '5', '6']: + birth_year += 1900 + elif gender_code in ['3', '4', '7', '8']: + birth_year += 2000 + else: + birth_year += 1900 + + gender = '남' if gender_code in ['1', '3', '5', '7'] else '여' + age = datetime.now().year - birth_year + except: + pass + + prescriptions.append({ + 'prescription_id': row.PreSerial, + 'order_number': row.Day_Serial, + 'date': row.PassDay, + 'patient_name': row.Paname, + 'patient_id': row.PaNum[:6] + '******' if row.PaNum and len(row.PaNum) > 6 else row.PaNum, + 'patient_code': row.CusCode, + 'hospital': row.InsName, + 'doctor': row.Drname, + 'time': row.PresTime, + 'type': '급여' if row.PreGubun == '0' else '비급여' if row.PreGubun == '9' else row.PreGubun, + 'age': age, + 'gender': gender, + 'price_total': row.PRICE_T, + 'price_patient': row.PRICE_P, + 'price_claim': row.PRICE_C + }) + + conn.close() + + return jsonify({ + 'success': True, + 'date': date_str, + 'count': len(prescriptions), + 'prescriptions': prescriptions + }) + + except Exception as e: + logging.error(f"PMR 처방전 목록 조회 오류: {e}") + return jsonify({'success': False, 'error': str(e)}), 500 + + +# ───────────────────────────────────────────────────────────── +# API: 처방전 상세 (약품 목록) +# ───────────────────────────────────────────────────────────── +@pmr_bp.route('/api/prescription/', methods=['GET']) +def get_prescription_detail(prescription_id): + """ + 처방전 상세 정보 (약품 목록 포함) + """ + try: + conn = get_mssql_connection('PM_PRES') + cursor = conn.cursor() + + # 처방전 기본 정보 + cursor.execute(""" + SELECT + PreSerial, + Day_Serial, + PassDay, + Paname, + PaNum, + CusCode, + InsName, + Drname, + PresTime, + PreGubun, + PRICE_T, + PRICE_P, + PRICE_C + FROM PS_MAIN + WHERE PreSerial = ? + """, (prescription_id,)) + + rx_row = cursor.fetchone() + if not rx_row: + conn.close() + return jsonify({'success': False, 'error': '처방전을 찾을 수 없습니다'}), 404 + + # 처방 약품 목록 (PS_sub_pharm 테이블 확인 필요) + cursor.execute(""" + SELECT COLUMN_NAME + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'PS_sub_pharm' + """) + sub_columns = [row[0] for row in cursor.fetchall()] + + # 약품 목록 조회 + medications = [] + if 'PreSerial' in sub_columns: + cursor.execute(""" + SELECT * + FROM PS_sub_pharm + WHERE PreSerial = ? + ORDER BY 1 + """, (prescription_id,)) + + med_columns = [col[0] for col in cursor.description] + for row in cursor.fetchall(): + med_dict = dict(zip(med_columns, row)) + medications.append({ + 'medication_code': med_dict.get('GoodsCode') or med_dict.get('PCODE') or '', + 'med_name': med_dict.get('GoodsName') or med_dict.get('PNAME') or '', + 'dosage': med_dict.get('Once_Qty') or med_dict.get('POESSION') or 0, + 'frequency': med_dict.get('Times') or med_dict.get('PTIMES') or 0, + 'duration': med_dict.get('Days') or med_dict.get('PDAY') or 0, + 'total_qty': med_dict.get('Total_Qty') or 0, + 'type': med_dict.get('PS_Type', '0') + }) + + conn.close() + + # 나이/성별 계산 + panum = rx_row.PaNum or '' + age = None + gender = None + if len(panum) >= 7: + try: + birth_year = int(panum[:2]) + gender_code = panum[6] + if gender_code in ['1', '2', '5', '6']: + birth_year += 1900 + elif gender_code in ['3', '4', '7', '8']: + birth_year += 2000 + gender = '남' if gender_code in ['1', '3', '5', '7'] else '여' + age = datetime.now().year - birth_year + except: + pass + + return jsonify({ + 'success': True, + 'prescription': { + 'prescription_id': rx_row.PreSerial, + 'order_number': rx_row.Day_Serial, + 'date': rx_row.PassDay, + 'time': rx_row.PresTime, + 'hospital': rx_row.InsName, + 'doctor': rx_row.Drname, + 'type': '급여' if rx_row.PreGubun == '0' else '비급여', + 'price_total': rx_row.PRICE_T, + 'price_patient': rx_row.PRICE_P + }, + 'patient': { + 'name': rx_row.Paname, + 'code': rx_row.CusCode, + 'age': age, + 'gender': gender + }, + 'medications': medications, + 'medication_count': len(medications) + }) + + except Exception as e: + logging.error(f"PMR 처방전 상세 조회 오류: {e}") + return jsonify({'success': False, 'error': str(e)}), 500 + + +# ───────────────────────────────────────────────────────────── +# API: 통계 (당일 요약) +# ───────────────────────────────────────────────────────────── +@pmr_bp.route('/api/stats', methods=['GET']) +def get_daily_stats(): + """당일 조제 통계""" + try: + date_str = request.args.get('date', date.today().strftime('%Y-%m-%d')) + date_yyyymmdd = date_str.replace('-', '') + + conn = get_mssql_connection('PM_PRES') + cursor = conn.cursor() + + # 처방전 수 + cursor.execute(""" + SELECT COUNT(*) as cnt + FROM PS_MAIN + WHERE PassDay = ? + """, (date_yyyymmdd,)) + total_prescriptions = cursor.fetchone()[0] + + # 총 금액 + cursor.execute(""" + SELECT + ISNULL(SUM(PRICE_T), 0) as total, + ISNULL(SUM(PRICE_P), 0) as patient, + ISNULL(SUM(PRICE_C), 0) as claim + FROM PS_MAIN + WHERE PassDay = ? + """, (date_yyyymmdd,)) + price_row = cursor.fetchone() + + conn.close() + + return jsonify({ + 'success': True, + 'date': date_str, + 'stats': { + 'total_prescriptions': total_prescriptions, + 'total_amount': price_row[0] if price_row else 0, + 'patient_amount': price_row[1] if price_row else 0, + 'claim_amount': price_row[2] if price_row else 0 + } + }) + + except Exception as e: + logging.error(f"PMR 통계 조회 오류: {e}") + return jsonify({'success': False, 'error': str(e)}), 500 + + +# ───────────────────────────────────────────────────────────── +# API: DB 연결 테스트 +# ───────────────────────────────────────────────────────────── +@pmr_bp.route('/api/test', methods=['GET']) +def test_connection(): + """DB 연결 테스트""" + try: + conn = get_mssql_connection('PM_PRES') + cursor = conn.cursor() + cursor.execute("SELECT @@VERSION") + version = cursor.fetchone()[0] + conn.close() + + return jsonify({ + 'success': True, + 'server': MSSQL_CONFIG['server'], + 'version': version[:100] + '...' + }) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 500 diff --git a/backend/templates/pmr.html b/backend/templates/pmr.html new file mode 100644 index 0000000..acb0b8d --- /dev/null +++ b/backend/templates/pmr.html @@ -0,0 +1,480 @@ + + + + + + 조제관리 - 청춘라벨 + + + + +
+

💊 조제관리 청춘라벨 v2

+
+ +
+
+
-
+
처방
+
+
+
-
+
환자
+
+
+
-
+
금액
+
+
+
+
+ + +
+ +
+
+ 📋 환자 목록 + 0명 +
+
+
+
+
+ + +
+ +
+
+
👈
+
환자를 선택하세요
+
+
+ +
+
+ + + +