feat(api): 기간별 사용약품 조회 API

- GET /api/drug-usage 엔드포인트 추가
- 조제건수/입고건수 통합 조회
- 조제일/소진일(expiry) 기준 선택 가능
- 약품명 검색, 약품코드 필터 지원
- QT_GUI 데이터와 100% 일치 검증됨 (살라겐정)
This commit is contained in:
thug0bin 2026-03-11 17:16:34 +09:00
parent 688bdb40f2
commit 04bf7a8535

View File

@ -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