feat(api): 기간별 사용약품 조회 API
- GET /api/drug-usage 엔드포인트 추가 - 조제건수/입고건수 통합 조회 - 조제일/소진일(expiry) 기준 선택 가능 - 약품명 검색, 약품코드 필터 지원 - QT_GUI 데이터와 100% 일치 검증됨 (살라겐정)
This commit is contained in:
parent
688bdb40f2
commit
04bf7a8535
209
backend/app.py
209
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__':
|
if __name__ == '__main__':
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user