From 04bf7a85356f661dcec72b0ff5d0a447020e0051 Mon Sep 17 00:00:00 2001 From: thug0bin Date: Wed, 11 Mar 2026 17:16:34 +0900 Subject: [PATCH] =?UTF-8?q?feat(api):=20=EA=B8=B0=EA=B0=84=EB=B3=84=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=95=BD=ED=92=88=20=EC=A1=B0=ED=9A=8C=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GET /api/drug-usage 엔드포인트 추가 - 조제건수/입고건수 통합 조회 - 조제일/소진일(expiry) 기준 선택 가능 - 약품명 검색, 약품코드 필터 지원 - QT_GUI 데이터와 100% 일치 검증됨 (살라겐정) --- backend/app.py | 209 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) diff --git a/backend/app.py b/backend/app.py index 4a54436..f00f39d 100644 --- a/backend/app.py +++ b/backend/app.py @@ -8619,6 +8619,215 @@ def api_animal_chat_logs(): }) +# ═══════════════════════════════════════════════════════════════════════════════ +# 기간별 사용약품 조회 API +# ═══════════════════════════════════════════════════════════════════════════════ + +@app.route('/api/drug-usage') +def api_drug_usage(): + """ + 기간별 사용약품 조회 API + + 파라미터: + - start_date: 시작일 (YYYYMMDD, 필수) + - end_date: 종료일 (YYYYMMDD, 필수) + - date_type: dispense (조제일, 기본) / expiry (소진일) + - drug_code: 특정 약품코드 필터 + - search: 약품명 검색 + - limit: 결과 제한 (기본 100) + """ + try: + # 파라미터 추출 + start_date = request.args.get('start_date') + end_date = request.args.get('end_date') + date_type = request.args.get('date_type', 'dispense') # dispense or expiry + drug_code = request.args.get('drug_code') + search = request.args.get('search') + limit = int(request.args.get('limit', 100)) + + # 필수 파라미터 확인 + if not start_date or not end_date: + return jsonify({ + 'success': False, + 'error': 'start_date와 end_date는 필수입니다 (YYYYMMDD 형식)' + }), 400 + + # 날짜 형식 검증 (8자리 숫자) + if not (start_date.isdigit() and len(start_date) == 8): + return jsonify({'success': False, 'error': 'start_date는 YYYYMMDD 형식이어야 합니다'}), 400 + if not (end_date.isdigit() and len(end_date) == 8): + return jsonify({'success': False, 'error': 'end_date는 YYYYMMDD 형식이어야 합니다'}), 400 + + pres_session = db_manager.get_session('PM_PRES') + + # ───────────────────────────────────────── + # 조제 데이터 쿼리 + # ───────────────────────────────────────── + if date_type == 'expiry': + # 소진일(조제만료일) 기준 필터 + rx_query = """ + SELECT + sp.DrugCode, + g.GoodsName, + m.PRINT_TYPE as category, + COUNT(*) as rx_count, + SUM(sp.QUAN * sp.QUAN_TIME * sp.Days) as total_qty + FROM PS_sub_pharm sp + INNER JOIN PS_main pm ON pm.PreSerial = sp.PreSerial + INNER JOIN PM_DRUG.dbo.CD_GOODS g ON sp.DrugCode = g.DrugCode + LEFT JOIN PM_DRUG.dbo.CD_MC m ON sp.DrugCode = m.DRUGCODE + WHERE sp.PS_Type != '9' + AND DATEADD(day, sp.Days, CONVERT(date, pm.Indate, 112)) + BETWEEN CONVERT(date, :start_date, 112) AND CONVERT(date, :end_date, 112) + """ + else: + # 조제일 기준 필터 (기본) + rx_query = """ + SELECT + sp.DrugCode, + g.GoodsName, + m.PRINT_TYPE as category, + COUNT(*) as rx_count, + SUM(sp.QUAN * sp.QUAN_TIME * sp.Days) as total_qty + FROM PS_sub_pharm sp + INNER JOIN PS_main pm ON pm.PreSerial = sp.PreSerial + INNER JOIN PM_DRUG.dbo.CD_GOODS g ON sp.DrugCode = g.DrugCode + LEFT JOIN PM_DRUG.dbo.CD_MC m ON sp.DrugCode = m.DRUGCODE + WHERE pm.Indate BETWEEN :start_date AND :end_date + AND sp.PS_Type != '9' + """ + + # 약품코드 필터 추가 + if drug_code: + rx_query += " AND sp.DrugCode = :drug_code" + + # 약품명 검색 필터 추가 + if search: + rx_query += " AND g.GoodsName LIKE :search" + + rx_query += " GROUP BY sp.DrugCode, g.GoodsName, m.PRINT_TYPE" + rx_query += " ORDER BY rx_count DESC" + + # 파라미터 바인딩 + params = {'start_date': start_date, 'end_date': end_date} + if drug_code: + params['drug_code'] = drug_code + if search: + params['search'] = f'%{search}%' + + rx_result = pres_session.execute(text(rx_query), params) + rx_data = {} + for row in rx_result: + rx_data[row.DrugCode] = { + 'drug_code': row.DrugCode, + 'goods_name': row.GoodsName, + 'category': row.category or '', + 'rx_count': row.rx_count, + 'rx_total_qty': float(row.total_qty) if row.total_qty else 0 + } + + # ───────────────────────────────────────── + # 입고 데이터 쿼리 (PM_DRUG에서 조회) + # ───────────────────────────────────────── + drug_session = db_manager.get_session('PM_DRUG') + + import_query = """ + SELECT + ws.DrugCode, + COUNT(*) as import_count, + SUM(ws.WH_NM_item_a) as total_qty + FROM WH_sub ws + INNER JOIN WH_main wm ON ws.WH_SR_stock = wm.WH_NO_stock + """ + + # 약품명 검색 시 CD_GOODS 조인 추가 + if search: + import_query += " INNER JOIN CD_GOODS g ON ws.DrugCode = g.DrugCode" + + import_query += " WHERE wm.WH_DT_appl BETWEEN :start_date AND :end_date" + + # 약품코드 필터 추가 + if drug_code: + import_query += " AND ws.DrugCode = :drug_code" + + # 약품명 검색 필터 추가 + if search: + import_query += " AND g.GoodsName LIKE :search" + + import_query += " GROUP BY ws.DrugCode" + + import_params = {'start_date': start_date, 'end_date': end_date} + if drug_code: + import_params['drug_code'] = drug_code + if search: + import_params['search'] = f'%{search}%' + + import_result = drug_session.execute(text(import_query), import_params) + import_data = {} + for row in import_result: + import_data[row.DrugCode] = { + 'import_count': row.import_count, + 'import_total_qty': float(row.total_qty) if row.total_qty else 0 + } + + # ───────────────────────────────────────── + # 결과 병합 (drug_code 기준) + # ───────────────────────────────────────── + all_drug_codes = set(rx_data.keys()) | set(import_data.keys()) + items = [] + + for code in all_drug_codes: + rx_info = rx_data.get(code, {}) + import_info = import_data.get(code, {}) + + item = { + 'drug_code': code, + 'goods_name': rx_info.get('goods_name', ''), + 'category': rx_info.get('category', ''), + 'rx_count': rx_info.get('rx_count', 0), + 'rx_total_qty': rx_info.get('rx_total_qty', 0), + 'import_count': import_info.get('import_count', 0), + 'import_total_qty': import_info.get('import_total_qty', 0) + } + + # 약품명이 없으면 (입고만 있는 경우) PM_DRUG에서 조회 + if not item['goods_name'] and code in import_data: + name_result = drug_session.execute( + text("SELECT GoodsName FROM CD_GOODS WHERE DrugCode = :code"), + {'code': code} + ).fetchone() + if name_result: + item['goods_name'] = name_result.GoodsName + + items.append(item) + + # 조제 건수 기준 정렬 + items.sort(key=lambda x: x['rx_count'], reverse=True) + + # limit 적용 + items = items[:limit] + + return jsonify({ + 'success': True, + 'period': { + 'start': start_date, + 'end': end_date, + 'date_type': date_type + }, + 'total_count': len(items), + 'items': items + }) + + except Exception as e: + logging.error(f"drug-usage API 오류: {e}") + import traceback + traceback.print_exc() + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + if __name__ == '__main__': import os