feat: 백제약품 API 통합
- baekje_api.py: Flask Blueprint 추가 - order_api.py: submit_baekje_order 함수 추가 - admin_rx_usage.html: WHOLESALERS에 baekje 추가 - 환경변수: BAEKJE_USER_ID, BAEKJE_PASSWORD URL: https://ibjp.co.kr (약국용 웹 주문 시스템)
This commit is contained in:
parent
78f6f21228
commit
857a058691
@ -65,6 +65,9 @@ app.register_blueprint(geoyoung_bp)
|
|||||||
from sooin_api import sooin_bp
|
from sooin_api import sooin_bp
|
||||||
app.register_blueprint(sooin_bp)
|
app.register_blueprint(sooin_bp)
|
||||||
|
|
||||||
|
from baekje_api import baekje_bp
|
||||||
|
app.register_blueprint(baekje_bp)
|
||||||
|
|
||||||
from order_api import order_bp
|
from order_api import order_bp
|
||||||
app.register_blueprint(order_bp)
|
app.register_blueprint(order_bp)
|
||||||
|
|
||||||
|
|||||||
208
backend/baekje_api.py
Normal file
208
backend/baekje_api.py
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
# -*- 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 BaekjeSession
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Blueprint 생성
|
||||||
|
baekje_bp = Blueprint('baekje', __name__, url_prefix='/api/baekje')
|
||||||
|
|
||||||
|
|
||||||
|
# ========== 세션 관리 ==========
|
||||||
|
|
||||||
|
_baekje_session = None
|
||||||
|
|
||||||
|
def get_baekje_session():
|
||||||
|
global _baekje_session
|
||||||
|
if _baekje_session is None:
|
||||||
|
_baekje_session = BaekjeSession()
|
||||||
|
return _baekje_session
|
||||||
|
|
||||||
|
|
||||||
|
def search_baekje_stock(keyword: str):
|
||||||
|
"""백제약품 재고 검색"""
|
||||||
|
try:
|
||||||
|
session = get_baekje_session()
|
||||||
|
result = session.search_products(keyword)
|
||||||
|
|
||||||
|
if result.get('success'):
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'keyword': keyword,
|
||||||
|
'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 ==========
|
||||||
|
|
||||||
|
@baekje_bp.route('/stock', methods=['GET'])
|
||||||
|
def api_baekje_stock():
|
||||||
|
"""
|
||||||
|
백제약품 재고 조회 API
|
||||||
|
|
||||||
|
GET /api/baekje/stock?kd_code=672300240
|
||||||
|
GET /api/baekje/stock?keyword=타이레놀
|
||||||
|
"""
|
||||||
|
kd_code = flask_request.args.get('kd_code', '').strip()
|
||||||
|
keyword = flask_request.args.get('keyword', '').strip()
|
||||||
|
|
||||||
|
search_term = kd_code or keyword
|
||||||
|
|
||||||
|
if not search_term:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': 'MISSING_PARAM',
|
||||||
|
'message': 'kd_code 또는 keyword 파라미터가 필요합니다'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = search_baekje_stock(search_term)
|
||||||
|
return jsonify(result)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"백제약품 API 오류: {e}")
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': 'API_ERROR',
|
||||||
|
'message': str(e)
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@baekje_bp.route('/session-status', methods=['GET'])
|
||||||
|
def api_session_status():
|
||||||
|
"""세션 상태 확인"""
|
||||||
|
session = get_baekje_session()
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'wholesaler': 'baekje',
|
||||||
|
'name': '백제약품',
|
||||||
|
'logged_in': session._logged_in,
|
||||||
|
'last_login': session._last_login,
|
||||||
|
'session_timeout': session.SESSION_TIMEOUT
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@baekje_bp.route('/login', methods=['POST'])
|
||||||
|
def api_login():
|
||||||
|
"""수동 로그인"""
|
||||||
|
session = get_baekje_session()
|
||||||
|
success = session.login()
|
||||||
|
return jsonify({
|
||||||
|
'success': success,
|
||||||
|
'message': '로그인 성공' if success else '로그인 실패'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@baekje_bp.route('/cart', methods=['GET'])
|
||||||
|
def api_get_cart():
|
||||||
|
"""장바구니 조회"""
|
||||||
|
session = get_baekje_session()
|
||||||
|
result = session.get_cart()
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
@baekje_bp.route('/cart', methods=['POST'])
|
||||||
|
def api_add_to_cart():
|
||||||
|
"""
|
||||||
|
장바구니 추가
|
||||||
|
|
||||||
|
POST /api/baekje/cart
|
||||||
|
{
|
||||||
|
"product_code": "672300240",
|
||||||
|
"quantity": 2
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
data = flask_request.get_json() or {}
|
||||||
|
product_code = data.get('product_code', '').strip()
|
||||||
|
quantity = int(data.get('quantity', 1))
|
||||||
|
|
||||||
|
if not product_code:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': 'MISSING_PARAM',
|
||||||
|
'message': 'product_code 필요'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
session = get_baekje_session()
|
||||||
|
result = session.add_to_cart(product_code, quantity)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
@baekje_bp.route('/order', methods=['POST'])
|
||||||
|
def api_submit_order():
|
||||||
|
"""
|
||||||
|
주문 등록
|
||||||
|
|
||||||
|
POST /api/baekje/order
|
||||||
|
{
|
||||||
|
"memo": "긴급 요청"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
data = flask_request.get_json() or {}
|
||||||
|
memo = data.get('memo', '')
|
||||||
|
|
||||||
|
session = get_baekje_session()
|
||||||
|
result = session.submit_order(memo)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
# ========== 프론트엔드 통합용 ==========
|
||||||
|
|
||||||
|
@baekje_bp.route('/search-for-order', methods=['POST'])
|
||||||
|
def api_search_for_order():
|
||||||
|
"""
|
||||||
|
발주용 재고 검색 (프론트엔드 통합용)
|
||||||
|
|
||||||
|
POST /api/baekje/search-for-order
|
||||||
|
{
|
||||||
|
"kd_code": "672300240",
|
||||||
|
"product_name": "타이레놀",
|
||||||
|
"specification": "500T"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
data = flask_request.get_json() or {}
|
||||||
|
kd_code = data.get('kd_code', '').strip()
|
||||||
|
product_name = data.get('product_name', '').strip()
|
||||||
|
specification = data.get('specification', '').strip()
|
||||||
|
|
||||||
|
search_term = kd_code or product_name
|
||||||
|
|
||||||
|
if not search_term:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': 'MISSING_PARAM'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
result = search_baekje_stock(search_term)
|
||||||
|
|
||||||
|
if result.get('success') and specification:
|
||||||
|
# 규격 필터링
|
||||||
|
filtered = [
|
||||||
|
item for item in result.get('items', [])
|
||||||
|
if specification.lower() in item.get('spec', '').lower()
|
||||||
|
]
|
||||||
|
result['items'] = filtered
|
||||||
|
result['count'] = len(filtered)
|
||||||
|
|
||||||
|
return jsonify(result)
|
||||||
@ -443,6 +443,8 @@ def api_quick_submit():
|
|||||||
submit_result = submit_geoyoung_order(order, dry_run)
|
submit_result = submit_geoyoung_order(order, dry_run)
|
||||||
elif order['wholesaler_id'] == 'sooin':
|
elif order['wholesaler_id'] == 'sooin':
|
||||||
submit_result = submit_sooin_order(order, dry_run)
|
submit_result = submit_sooin_order(order, dry_run)
|
||||||
|
elif order['wholesaler_id'] == 'baekje':
|
||||||
|
submit_result = submit_baekje_order(order, dry_run)
|
||||||
else:
|
else:
|
||||||
submit_result = {'success': False, 'error': f"Wholesaler {order['wholesaler_id']} not supported"}
|
submit_result = {'success': False, 'error': f"Wholesaler {order['wholesaler_id']} not supported"}
|
||||||
|
|
||||||
@ -661,6 +663,202 @@ def submit_sooin_order(order: dict, dry_run: bool) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def submit_baekje_order(order: dict, dry_run: bool) -> dict:
|
||||||
|
"""백제약품 주문 제출"""
|
||||||
|
order_id = order['id']
|
||||||
|
items = order['items']
|
||||||
|
|
||||||
|
# 상태 업데이트
|
||||||
|
update_order_status(order_id, 'pending',
|
||||||
|
f'백제약품 주문 시작 (dry_run={dry_run})')
|
||||||
|
|
||||||
|
results = []
|
||||||
|
success_count = 0
|
||||||
|
failed_count = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
from baekje_api import get_baekje_session
|
||||||
|
baekje_session = get_baekje_session()
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
# ─────────────────────────────────────────
|
||||||
|
# DRY RUN: 재고 확인만
|
||||||
|
# ─────────────────────────────────────────
|
||||||
|
for item in items:
|
||||||
|
kd_code = item.get('kd_code') or item.get('drug_code')
|
||||||
|
spec = item.get('specification', '')
|
||||||
|
|
||||||
|
# 재고 검색
|
||||||
|
search_result = baekje_session.search_products(kd_code)
|
||||||
|
|
||||||
|
matched = None
|
||||||
|
available_specs = []
|
||||||
|
spec_stocks = {}
|
||||||
|
|
||||||
|
if search_result.get('success'):
|
||||||
|
for baekje_item in search_result.get('items', []):
|
||||||
|
s = baekje_item.get('spec', '')
|
||||||
|
available_specs.append(s)
|
||||||
|
spec_stocks[s] = baekje_item.get('stock', 0)
|
||||||
|
|
||||||
|
# 규격 매칭
|
||||||
|
if spec in s or s in spec:
|
||||||
|
if matched is None or baekje_item.get('stock', 0) > matched.get('stock', 0):
|
||||||
|
matched = baekje_item
|
||||||
|
|
||||||
|
if matched:
|
||||||
|
stock = matched.get('stock', 0)
|
||||||
|
if stock >= item['order_qty']:
|
||||||
|
status = 'success'
|
||||||
|
result_code = 'OK'
|
||||||
|
result_message = f"[DRY RUN] 주문 가능: 재고 {stock}, 단가 {matched.get('price', 0):,}원"
|
||||||
|
success_count += 1
|
||||||
|
selection_reason = 'stock_available'
|
||||||
|
elif stock > 0:
|
||||||
|
status = 'failed'
|
||||||
|
result_code = 'LOW_STOCK'
|
||||||
|
result_message = f"[DRY RUN] 재고 부족: {stock}개 (요청: {item['order_qty']})"
|
||||||
|
failed_count += 1
|
||||||
|
selection_reason = 'low_stock'
|
||||||
|
else:
|
||||||
|
status = 'failed'
|
||||||
|
result_code = 'OUT_OF_STOCK'
|
||||||
|
result_message = f"[DRY RUN] 재고 없음"
|
||||||
|
failed_count += 1
|
||||||
|
selection_reason = 'out_of_stock'
|
||||||
|
else:
|
||||||
|
status = 'failed'
|
||||||
|
result_code = 'NOT_FOUND'
|
||||||
|
result_message = f"[DRY RUN] 백제에서 규격 {spec} 미발견"
|
||||||
|
failed_count += 1
|
||||||
|
selection_reason = 'not_found'
|
||||||
|
|
||||||
|
update_item_result(item['id'], status, result_code, result_message)
|
||||||
|
|
||||||
|
# AI 학습용 컨텍스트 저장
|
||||||
|
save_order_context(item['id'], {
|
||||||
|
'drug_code': item['drug_code'],
|
||||||
|
'product_name': item['product_name'],
|
||||||
|
'stock_at_order': item.get('current_stock', 0),
|
||||||
|
'usage_7d': item.get('usage_qty', 0),
|
||||||
|
'ordered_spec': spec,
|
||||||
|
'ordered_qty': item['order_qty'],
|
||||||
|
'available_specs': available_specs,
|
||||||
|
'spec_stocks': spec_stocks,
|
||||||
|
'selection_reason': selection_reason,
|
||||||
|
'wholesaler_id': 'baekje'
|
||||||
|
})
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
'item_id': item['id'],
|
||||||
|
'drug_code': item['drug_code'],
|
||||||
|
'product_name': item['product_name'],
|
||||||
|
'specification': spec,
|
||||||
|
'order_qty': item['order_qty'],
|
||||||
|
'status': status,
|
||||||
|
'result_code': result_code,
|
||||||
|
'result_message': result_message,
|
||||||
|
'available_specs': available_specs,
|
||||||
|
'spec_stocks': spec_stocks,
|
||||||
|
'price': matched.get('price') if matched else None
|
||||||
|
})
|
||||||
|
|
||||||
|
# 상태 업데이트
|
||||||
|
if failed_count == 0:
|
||||||
|
update_order_status(order_id, 'completed',
|
||||||
|
f'[DRY RUN] 백제 시뮬레이션 완료: {success_count}개 성공')
|
||||||
|
elif success_count == 0:
|
||||||
|
update_order_status(order_id, 'failed',
|
||||||
|
f'[DRY RUN] 백제 시뮬레이션 완료: {failed_count}개 실패')
|
||||||
|
else:
|
||||||
|
update_order_status(order_id, 'partial',
|
||||||
|
f'[DRY RUN] 백제 부분 성공: {success_count}개 성공, {failed_count}개 실패')
|
||||||
|
|
||||||
|
else:
|
||||||
|
# ─────────────────────────────────────────
|
||||||
|
# 실제 주문 (장바구니 추가)
|
||||||
|
# ─────────────────────────────────────────
|
||||||
|
for item in items:
|
||||||
|
kd_code = item.get('kd_code') or item.get('drug_code')
|
||||||
|
order_qty = item['order_qty']
|
||||||
|
spec = item.get('specification', '')
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 장바구니 추가
|
||||||
|
cart_result = baekje_session.add_to_cart(kd_code, order_qty)
|
||||||
|
|
||||||
|
if cart_result.get('success'):
|
||||||
|
status = 'success'
|
||||||
|
result_code = 'CART_ADDED'
|
||||||
|
result_message = f"장바구니 추가 완료 (백제몰에서 확정 필요)"
|
||||||
|
success_count += 1
|
||||||
|
else:
|
||||||
|
status = 'failed'
|
||||||
|
result_code = cart_result.get('error', 'CART_FAILED')
|
||||||
|
result_message = cart_result.get('message', '장바구니 추가 실패')
|
||||||
|
failed_count += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
status = 'failed'
|
||||||
|
result_code = 'ERROR'
|
||||||
|
result_message = str(e)
|
||||||
|
failed_count += 1
|
||||||
|
|
||||||
|
update_item_result(item['id'], status, result_code, result_message)
|
||||||
|
|
||||||
|
save_order_context(item['id'], {
|
||||||
|
'drug_code': item['drug_code'],
|
||||||
|
'product_name': item['product_name'],
|
||||||
|
'stock_at_order': item.get('current_stock', 0),
|
||||||
|
'usage_7d': item.get('usage_qty', 0),
|
||||||
|
'ordered_spec': spec,
|
||||||
|
'ordered_qty': order_qty,
|
||||||
|
'selection_reason': 'user_order',
|
||||||
|
'wholesaler_id': 'baekje'
|
||||||
|
})
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
'item_id': item['id'],
|
||||||
|
'drug_code': item['drug_code'],
|
||||||
|
'product_name': item['product_name'],
|
||||||
|
'specification': spec,
|
||||||
|
'order_qty': order_qty,
|
||||||
|
'status': status,
|
||||||
|
'result_code': result_code,
|
||||||
|
'result_message': result_message
|
||||||
|
})
|
||||||
|
|
||||||
|
# 상태 업데이트
|
||||||
|
if success_count > 0:
|
||||||
|
update_order_status(order_id, 'pending',
|
||||||
|
f'백제 장바구니 추가 완료: {success_count}개 (확정 필요)')
|
||||||
|
else:
|
||||||
|
update_order_status(order_id, 'failed',
|
||||||
|
f'백제 주문 실패: {failed_count}개')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'dry_run': dry_run,
|
||||||
|
'order_id': order_id,
|
||||||
|
'order_no': order['order_no'],
|
||||||
|
'wholesaler': 'baekje',
|
||||||
|
'total_items': len(items),
|
||||||
|
'success_count': success_count,
|
||||||
|
'failed_count': failed_count,
|
||||||
|
'results': results,
|
||||||
|
'note': '실제 주문 시 장바구니에 담김. 백제몰(ibjp.co.kr)에서 최종 확정 필요.' if not dry_run else None
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"백제 주문 오류: {e}")
|
||||||
|
update_order_status(order_id, 'failed', str(e))
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'order_id': order_id,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────
|
# ─────────────────────────────────────────────
|
||||||
# AI 학습용 API
|
# AI 학습용 API
|
||||||
# ─────────────────────────────────────────────
|
# ─────────────────────────────────────────────
|
||||||
|
|||||||
@ -1072,8 +1072,17 @@
|
|||||||
gradient: 'linear-gradient(135deg, #7c3aed, #a855f7)',
|
gradient: 'linear-gradient(135deg, #7c3aed, #a855f7)',
|
||||||
filterFn: (item) => item.supplier === '수인약품' || item.wholesaler === 'sooin',
|
filterFn: (item) => item.supplier === '수인약품' || item.wholesaler === 'sooin',
|
||||||
getCode: (item) => item.sooin_code || item.drug_code
|
getCode: (item) => item.sooin_code || item.drug_code
|
||||||
|
},
|
||||||
|
baekje: {
|
||||||
|
id: 'baekje',
|
||||||
|
name: '백제약품',
|
||||||
|
icon: '💉',
|
||||||
|
logo: '/static/img/logo_baekje.png',
|
||||||
|
color: '#f59e0b',
|
||||||
|
gradient: 'linear-gradient(135deg, #d97706, #f59e0b)',
|
||||||
|
filterFn: (item) => item.supplier === '백제약품' || item.wholesaler === 'baekje',
|
||||||
|
getCode: (item) => item.baekje_code || item.drug_code
|
||||||
}
|
}
|
||||||
// 향후 추가: baekje, etc.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ──────────────── 주문 제출 ────────────────
|
// ──────────────── 주문 제출 ────────────────
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user