## APC 코드 체계 확장 - 기존: 023%만 검색 (~2023년 제품만) - 변경: 02% OR 92% + 13자리 검증 - 02%: 2023년 이전 item_seq (9자리) 기반 APC - 92%: 2024년 이후 item_seq (10자리) 기반 APC - 999% 등 청구프로그램 임의코드는 제외 ## 동물약 챗봇 피부약 추천 개선 - 피부약 2단계 추천 구조 추가 - 1차(치료): 의약품 (개시딘겔, 테르비덤 등) - 2차(보조케어): 의약외품 (스킨카솔 - 회복기 피부보호) - 스킨카솔은 의약외품임을 명시하여 치료제로 오인 방지 ## 기타 - RAG 테스트 스크립트 추가 - 수인약품 API 문서화
707 lines
21 KiB
Python
707 lines
21 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
수인약품 도매상 API - Flask Blueprint
|
|
|
|
핵심 로직은 wholesale 패키지에서 가져옴
|
|
이 파일은 Flask 웹 API 연동만 담당
|
|
"""
|
|
|
|
import time
|
|
import logging
|
|
|
|
from flask import Blueprint, jsonify, request as flask_request
|
|
|
|
# wholesale 패키지 경로 설정
|
|
import wholesale_path
|
|
|
|
# wholesale 패키지에서 핵심 클래스 가져오기
|
|
from wholesale import SooinSession
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Blueprint 생성
|
|
sooin_bp = Blueprint('sooin', __name__, url_prefix='/api/sooin')
|
|
|
|
|
|
# ========== 세션 관리 ==========
|
|
|
|
_sooin_session = None
|
|
|
|
def get_sooin_session():
|
|
global _sooin_session
|
|
if _sooin_session is None:
|
|
_sooin_session = SooinSession()
|
|
return _sooin_session
|
|
|
|
|
|
def search_sooin_stock(keyword: str, search_type: str = 'kd_code'):
|
|
"""수인약품 재고 검색 (동기, 빠름)"""
|
|
try:
|
|
session = get_sooin_session()
|
|
result = session.search_products(keyword)
|
|
|
|
if result.get('success'):
|
|
return {
|
|
'success': True,
|
|
'keyword': keyword,
|
|
'search_type': search_type,
|
|
'count': result['total'],
|
|
'items': result['items']
|
|
}
|
|
else:
|
|
return result
|
|
|
|
except Exception as e:
|
|
logger.error(f"수인약품 검색 오류: {e}")
|
|
return {'success': False, 'error': 'SEARCH_ERROR', 'message': str(e)}
|
|
|
|
|
|
# ========== Flask API Routes ==========
|
|
|
|
@sooin_bp.route('/stock', methods=['GET'])
|
|
def api_sooin_stock():
|
|
"""
|
|
수인약품 재고 조회 API
|
|
|
|
GET /api/sooin/stock?kd_code=073100220
|
|
GET /api/sooin/stock?keyword=코자정&type=name
|
|
"""
|
|
kd_code = flask_request.args.get('kd_code', '').strip()
|
|
keyword = flask_request.args.get('keyword', '').strip()
|
|
search_type = flask_request.args.get('type', 'kd_code').strip()
|
|
|
|
search_term = kd_code or keyword
|
|
if kd_code:
|
|
search_type = 'kd_code'
|
|
|
|
if not search_term:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'MISSING_PARAM',
|
|
'message': 'kd_code 또는 keyword 파라미터가 필요합니다'
|
|
}), 400
|
|
|
|
try:
|
|
result = search_sooin_stock(search_term, search_type)
|
|
return jsonify(result)
|
|
except Exception as e:
|
|
logger.error(f"수인약품 API 오류: {e}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'API_ERROR',
|
|
'message': str(e)
|
|
}), 500
|
|
|
|
|
|
@sooin_bp.route('/session-status', methods=['GET'])
|
|
def api_session_status():
|
|
"""세션 상태 확인"""
|
|
session = get_sooin_session()
|
|
return jsonify({
|
|
'logged_in': session._logged_in,
|
|
'last_login': session._last_login,
|
|
'session_age_sec': int(time.time() - session._last_login) if session._last_login else None
|
|
})
|
|
|
|
|
|
@sooin_bp.route('/balance', methods=['GET'])
|
|
def api_sooin_balance():
|
|
"""
|
|
수인약품 잔고(미수금) 조회 API
|
|
|
|
GET /api/sooin/balance
|
|
|
|
Returns:
|
|
{
|
|
"success": true,
|
|
"balance": 14293001, // 현재 잔고 (누계합)
|
|
"prev_balance": 10592762, // 전일잔액
|
|
"monthly_sales": 3700239, // 월 매출
|
|
"yearly_sales": 34380314 // 연 누계 매출
|
|
}
|
|
"""
|
|
try:
|
|
session = get_sooin_session()
|
|
result = session.get_balance()
|
|
return jsonify(result)
|
|
except Exception as e:
|
|
logger.error(f"수인약품 잔고 조회 오류: {e}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'BALANCE_ERROR',
|
|
'message': str(e),
|
|
'balance': 0
|
|
}), 500
|
|
|
|
|
|
@sooin_bp.route('/monthly-sales', methods=['GET'])
|
|
def api_sooin_monthly_sales():
|
|
"""
|
|
수인약품 월간 매출 조회 API
|
|
|
|
GET /api/sooin/monthly-sales?year=2026&month=3
|
|
|
|
Returns:
|
|
{
|
|
"success": true,
|
|
"total_amount": 3700239, // 월간 매출 합계
|
|
"total_paid": 0, // 월간 입금 합계
|
|
"ending_balance": 14293001, // 월말 잔액
|
|
"opening_balance": 10592762, // 전일(기초) 잔액
|
|
"from_date": "2026-03-01",
|
|
"to_date": "2026-03-31"
|
|
}
|
|
"""
|
|
from datetime import datetime
|
|
|
|
year = flask_request.args.get('year', type=int)
|
|
month = flask_request.args.get('month', type=int)
|
|
|
|
# 기본값: 현재 월
|
|
if not year or not month:
|
|
now = datetime.now()
|
|
year = year or now.year
|
|
month = month or now.month
|
|
|
|
try:
|
|
session = get_sooin_session()
|
|
if hasattr(session, 'get_monthly_sales'):
|
|
result = session.get_monthly_sales(year, month)
|
|
return jsonify(result)
|
|
else:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'NOT_IMPLEMENTED',
|
|
'message': '수인약품 월간 매출 조회 미구현'
|
|
}), 501
|
|
except Exception as e:
|
|
logger.error(f"수인약품 월간 매출 조회 오류: {e}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'MONTHLY_SALES_ERROR',
|
|
'message': str(e)
|
|
}), 500
|
|
|
|
|
|
@sooin_bp.route('/cart', methods=['GET'])
|
|
def api_sooin_cart():
|
|
"""장바구니 조회 API"""
|
|
try:
|
|
session = get_sooin_session()
|
|
result = session.get_cart()
|
|
return jsonify(result)
|
|
except Exception as e:
|
|
return jsonify({'success': False, 'error': str(e), 'items': []}), 500
|
|
|
|
|
|
@sooin_bp.route('/cart/clear', methods=['POST'])
|
|
def api_sooin_cart_clear():
|
|
"""장바구니 비우기 API"""
|
|
try:
|
|
session = get_sooin_session()
|
|
result = session.clear_cart()
|
|
return jsonify(result)
|
|
except Exception as e:
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
@sooin_bp.route('/cart/cancel', methods=['POST'])
|
|
def api_sooin_cart_cancel():
|
|
"""
|
|
장바구니 항목 취소 API
|
|
|
|
POST /api/sooin/cart/cancel
|
|
{ "row_index": 0 }
|
|
또는
|
|
{ "internal_code": "32495" }
|
|
"""
|
|
data = flask_request.get_json() or {}
|
|
row_index = data.get('row_index')
|
|
internal_code = data.get('internal_code')
|
|
|
|
if row_index is None and not internal_code:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'MISSING_PARAM',
|
|
'message': 'row_index 또는 internal_code가 필요합니다'
|
|
}), 400
|
|
|
|
try:
|
|
session = get_sooin_session()
|
|
result = session.cancel_item(row_index=row_index, product_code=internal_code)
|
|
return jsonify(result)
|
|
except Exception as e:
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
@sooin_bp.route('/cart/restore', methods=['POST'])
|
|
def api_sooin_cart_restore():
|
|
"""
|
|
취소된 항목 복원 API
|
|
|
|
POST /api/sooin/cart/restore
|
|
{ "row_index": 0 }
|
|
"""
|
|
data = flask_request.get_json() or {}
|
|
row_index = data.get('row_index')
|
|
internal_code = data.get('internal_code')
|
|
|
|
try:
|
|
session = get_sooin_session()
|
|
result = session.restore_item(row_index=row_index, product_code=internal_code)
|
|
return jsonify(result)
|
|
except Exception as e:
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
@sooin_bp.route('/order', methods=['POST'])
|
|
def api_sooin_order():
|
|
"""
|
|
수인약품 주문 API (장바구니 추가)
|
|
|
|
POST /api/sooin/order
|
|
{
|
|
"kd_code": "073100220",
|
|
"quantity": 1,
|
|
"specification": "30T",
|
|
"check_stock": true
|
|
}
|
|
"""
|
|
data = flask_request.get_json()
|
|
|
|
if not data:
|
|
return jsonify({'success': False, 'error': 'NO_DATA'}), 400
|
|
|
|
kd_code = data.get('kd_code', '').strip()
|
|
quantity = data.get('quantity', 1)
|
|
specification = data.get('specification')
|
|
check_stock = data.get('check_stock', True)
|
|
|
|
if not kd_code:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'MISSING_PARAM',
|
|
'message': 'kd_code가 필요합니다'
|
|
}), 400
|
|
|
|
try:
|
|
session = get_sooin_session()
|
|
result = session.quick_order(
|
|
kd_code=kd_code,
|
|
quantity=quantity,
|
|
spec=specification,
|
|
check_stock=check_stock
|
|
)
|
|
return jsonify(result)
|
|
except Exception as e:
|
|
logger.error(f"수인약품 주문 오류: {e}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'ORDER_ERROR',
|
|
'message': str(e)
|
|
}), 500
|
|
|
|
|
|
@sooin_bp.route('/confirm', methods=['POST'])
|
|
def api_sooin_confirm():
|
|
"""주문 확정 API"""
|
|
data = flask_request.get_json() or {}
|
|
memo = data.get('memo', '')
|
|
|
|
try:
|
|
session = get_sooin_session()
|
|
result = session.submit_order(memo)
|
|
return jsonify(result)
|
|
except Exception as e:
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
@sooin_bp.route('/full-order', methods=['POST'])
|
|
def api_sooin_full_order():
|
|
"""
|
|
전체 주문 API (검색 → 장바구니 → 확정)
|
|
|
|
POST /api/sooin/full-order
|
|
{
|
|
"kd_code": "073100220",
|
|
"quantity": 1,
|
|
"specification": "30T",
|
|
"auto_confirm": true,
|
|
"memo": "자동주문"
|
|
}
|
|
"""
|
|
data = flask_request.get_json()
|
|
|
|
if not data or not data.get('kd_code'):
|
|
return jsonify({'success': False, 'error': 'kd_code required'}), 400
|
|
|
|
try:
|
|
session = get_sooin_session()
|
|
|
|
# 장바구니에 담기
|
|
cart_result = session.quick_order(
|
|
kd_code=data['kd_code'],
|
|
quantity=data.get('quantity', 1),
|
|
spec=data.get('specification'),
|
|
check_stock=data.get('check_stock', True)
|
|
)
|
|
|
|
if not cart_result.get('success'):
|
|
return jsonify(cart_result)
|
|
|
|
if not data.get('auto_confirm', True):
|
|
return jsonify(cart_result)
|
|
|
|
# 주문 확정
|
|
confirm_result = session.submit_order(data.get('memo', ''))
|
|
|
|
if confirm_result.get('success'):
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f"{cart_result['product']['name']} {cart_result['quantity']}개 주문 완료",
|
|
'product': cart_result['product'],
|
|
'quantity': cart_result['quantity'],
|
|
'confirmed': True
|
|
})
|
|
else:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': confirm_result.get('error', 'CONFIRM_FAILED'),
|
|
'message': f"장바구니 담기 성공, 주문 확정 실패",
|
|
'product': cart_result['product'],
|
|
'cart_added': True
|
|
})
|
|
except Exception as e:
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
@sooin_bp.route('/order-batch', methods=['POST'])
|
|
def api_sooin_order_batch():
|
|
"""수인약품 일괄 주문 API"""
|
|
data = flask_request.get_json()
|
|
|
|
if not data or not data.get('items'):
|
|
return jsonify({'success': False, 'error': 'NO_ITEMS'}), 400
|
|
|
|
items = data.get('items', [])
|
|
check_stock = data.get('check_stock', True)
|
|
|
|
session = get_sooin_session()
|
|
results = []
|
|
success_count = 0
|
|
failed_count = 0
|
|
|
|
for item in items:
|
|
kd_code = item.get('kd_code', '').strip()
|
|
quantity = item.get('quantity', 1)
|
|
specification = item.get('specification')
|
|
|
|
if not kd_code:
|
|
results.append({
|
|
'kd_code': kd_code,
|
|
'success': False,
|
|
'error': 'MISSING_KD_CODE'
|
|
})
|
|
failed_count += 1
|
|
continue
|
|
|
|
try:
|
|
result = session.quick_order(
|
|
kd_code=kd_code,
|
|
quantity=quantity,
|
|
spec=specification,
|
|
check_stock=check_stock
|
|
)
|
|
result['kd_code'] = kd_code
|
|
result['requested_qty'] = quantity
|
|
results.append(result)
|
|
|
|
if result.get('success'):
|
|
success_count += 1
|
|
else:
|
|
failed_count += 1
|
|
|
|
except Exception as e:
|
|
results.append({
|
|
'kd_code': kd_code,
|
|
'success': False,
|
|
'error': 'EXCEPTION',
|
|
'message': str(e)
|
|
})
|
|
failed_count += 1
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'total': len(items),
|
|
'success_count': success_count,
|
|
'failed_count': failed_count,
|
|
'results': results
|
|
})
|
|
|
|
|
|
# ========== 주문 조회 API ==========
|
|
|
|
@sooin_bp.route('/orders', methods=['GET'])
|
|
def api_sooin_orders():
|
|
"""
|
|
수인약품 주문 목록 조회 API
|
|
|
|
GET /api/sooin/orders?start_date=2026-03-01&end_date=2026-03-07
|
|
|
|
파라미터:
|
|
start_date: 시작일 (YYYY-MM-DD), 기본값: 오늘
|
|
end_date: 종료일 (YYYY-MM-DD), 기본값: 오늘
|
|
|
|
Returns:
|
|
{
|
|
"success": true,
|
|
"orders": [
|
|
{
|
|
"order_num": "202603095091177",
|
|
"order_date": "2026-03-09",
|
|
"order_time": "14:30:25",
|
|
"total_amount": 125000,
|
|
"item_count": 5,
|
|
"status": "완료"
|
|
}
|
|
],
|
|
"total_count": 10
|
|
}
|
|
"""
|
|
from datetime import datetime
|
|
|
|
today = datetime.now().strftime("%Y-%m-%d")
|
|
start_date = flask_request.args.get('start_date', today).strip()
|
|
end_date = flask_request.args.get('end_date', today).strip()
|
|
|
|
try:
|
|
session = get_sooin_session()
|
|
result = session.get_order_list(start_date, end_date)
|
|
return jsonify(result)
|
|
except Exception as e:
|
|
logger.error(f"수인약품 주문 목록 조회 오류: {e}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'ORDERS_ERROR',
|
|
'message': str(e),
|
|
'orders': [],
|
|
'total_count': 0
|
|
}), 500
|
|
|
|
|
|
@sooin_bp.route('/orders/today-summary', methods=['GET'])
|
|
def api_sooin_today_summary():
|
|
"""
|
|
수인약품 오늘 주문 집계 API
|
|
|
|
GET /api/sooin/orders/today-summary
|
|
|
|
Returns:
|
|
{
|
|
"success": true,
|
|
"date": "2026-03-09",
|
|
"summary": [
|
|
{
|
|
"kd_code": "073100220",
|
|
"product_name": "코자정50mg",
|
|
"total_quantity": 10,
|
|
"total_amount": 150000,
|
|
"order_count": 3
|
|
}
|
|
],
|
|
"grand_total_amount": 500000,
|
|
"grand_total_items": 25,
|
|
"order_count": 5
|
|
}
|
|
"""
|
|
try:
|
|
session = get_sooin_session()
|
|
result = session.get_today_order_summary()
|
|
return jsonify(result)
|
|
except Exception as e:
|
|
logger.error(f"수인약품 오늘 주문 집계 오류: {e}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'TODAY_SUMMARY_ERROR',
|
|
'message': str(e),
|
|
'summary': [],
|
|
'grand_total_amount': 0,
|
|
'grand_total_items': 0,
|
|
'order_count': 0
|
|
}), 500
|
|
|
|
|
|
@sooin_bp.route('/orders/<order_num>', methods=['GET'])
|
|
def api_sooin_order_detail(order_num):
|
|
"""
|
|
수인약품 주문 상세 조회 API
|
|
|
|
GET /api/sooin/orders/202603095091177
|
|
|
|
Returns:
|
|
{
|
|
"success": true,
|
|
"order_num": "202603095091177",
|
|
"order_date": "2026-03-09",
|
|
"items": [
|
|
{
|
|
"product_code": "32495",
|
|
"kd_code": "073100220",
|
|
"product_name": "코자정50mg",
|
|
"spec": "30T",
|
|
"quantity": 2,
|
|
"unit_price": 15000,
|
|
"amount": 30000
|
|
}
|
|
],
|
|
"total_amount": 125000,
|
|
"item_count": 5
|
|
}
|
|
"""
|
|
if not order_num or not order_num.isdigit():
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'INVALID_ORDER_NUM',
|
|
'message': '유효한 주문번호를 입력하세요'
|
|
}), 400
|
|
|
|
try:
|
|
session = get_sooin_session()
|
|
result = session.get_order_detail(order_num)
|
|
return jsonify(result)
|
|
except Exception as e:
|
|
logger.error(f"수인약품 주문 상세 조회 오류: {e}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'ORDER_DETAIL_ERROR',
|
|
'message': str(e),
|
|
'order_num': order_num,
|
|
'items': [],
|
|
'total_amount': 0
|
|
}), 500
|
|
|
|
|
|
@sooin_bp.route('/orders/summary-by-kd', methods=['GET'])
|
|
def api_sooin_orders_by_kd():
|
|
"""
|
|
수인약품 주문량 KD코드별 집계 API (병렬 처리)
|
|
|
|
GET /api/sooin/orders/summary-by-kd?start_date=2026-03-01&end_date=2026-03-07
|
|
"""
|
|
import re
|
|
from datetime import datetime
|
|
|
|
today = datetime.now().strftime("%Y-%m-%d")
|
|
start_date = flask_request.args.get('start_date', today).strip()
|
|
end_date = flask_request.args.get('end_date', today).strip()
|
|
|
|
def parse_spec(spec: str) -> int:
|
|
"""
|
|
규격에서 박스당 단위 수 추출
|
|
|
|
정량 단위 (T, 정, 캡슐, C, PTP, 포 등): 숫자 추출
|
|
용량 단위 (g, ml, mL, mg, L 등): 1 반환 (튜브/병 단위)
|
|
|
|
예시:
|
|
- '30T' → 30 (정제 30정)
|
|
- '100정(PTP)' → 100
|
|
- '15g' → 1 (튜브 1개)
|
|
- '10ml' → 1 (병 1개)
|
|
- '500mg' → 1 (용량 표시)
|
|
"""
|
|
if not spec:
|
|
return 1
|
|
|
|
spec_lower = spec.lower()
|
|
|
|
# 용량 단위 패턴: 숫자 + g/ml/mg/l (단독 또는 끝)
|
|
# 이 경우 튜브/병 단위이므로 1 반환
|
|
volume_pattern = r'^\d+\s*(g|ml|mg|l)(\s|$|\)|/)'
|
|
if re.search(volume_pattern, spec_lower):
|
|
return 1
|
|
|
|
# 정량 단위 패턴: 숫자 + T/정/캡슐/C/PTP/포
|
|
qty_pattern = r'(\d+)\s*(t|정|캡슐?|c|ptp|포|tab|cap)'
|
|
qty_match = re.search(qty_pattern, spec_lower)
|
|
if qty_match:
|
|
return int(qty_match.group(1))
|
|
|
|
# 기본: 숫자만 있으면 추출하되, 용량 단위 재확인
|
|
# 끝에 g/ml이 있으면 1 반환
|
|
if re.search(r'\d+(g|ml)$', spec_lower):
|
|
return 1
|
|
|
|
# 그 외 숫자 추출
|
|
match = re.search(r'(\d+)', spec)
|
|
return int(match.group(1)) if match else 1
|
|
|
|
try:
|
|
session = get_sooin_session()
|
|
|
|
# 주문 목록 조회
|
|
orders_result = session.get_order_list(start_date, end_date)
|
|
|
|
if not orders_result.get('success'):
|
|
return jsonify({
|
|
'success': False,
|
|
'error': orders_result.get('error', 'ORDERS_FETCH_FAILED'),
|
|
'by_kd_code': {}
|
|
})
|
|
|
|
orders = orders_result.get('orders', [])
|
|
order_nums = [o.get('order_num') for o in orders if o.get('order_num')]
|
|
|
|
# 순차 처리 + 캐시 (캐시 효과 극대화)
|
|
all_details = []
|
|
|
|
for order_num in order_nums:
|
|
try:
|
|
detail = session.get_order_detail(order_num)
|
|
if detail.get('success'):
|
|
all_details.append(detail)
|
|
except Exception as e:
|
|
logger.warning(f"주문 상세 조회 실패: {e}")
|
|
|
|
# KD코드별 집계
|
|
kd_summary = {}
|
|
|
|
for detail in all_details:
|
|
for item in detail.get('items', []):
|
|
kd_code = item.get('kd_code', '')
|
|
if not kd_code:
|
|
continue
|
|
|
|
product_name = item.get('product_name', '')
|
|
spec = item.get('spec', '')
|
|
quantity = item.get('quantity', 0)
|
|
per_unit = parse_spec(spec)
|
|
total_units = quantity * per_unit
|
|
|
|
if kd_code not in kd_summary:
|
|
kd_summary[kd_code] = {
|
|
'product_name': product_name,
|
|
'spec': spec,
|
|
'boxes': 0,
|
|
'units': 0
|
|
}
|
|
|
|
kd_summary[kd_code]['boxes'] += quantity
|
|
kd_summary[kd_code]['units'] += total_units
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'order_count': len(order_nums),
|
|
'period': {'start': start_date, 'end': end_date},
|
|
'by_kd_code': kd_summary,
|
|
'total_products': len(kd_summary)
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"수인약품 KD코드별 집계 오류: {e}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'SUMMARY_ERROR',
|
|
'message': str(e),
|
|
'by_kd_code': {}
|
|
}), 500
|