feat: 수인약품 주문 dry-run 지원

- order_api.py: submit_sooin_order() 함수 추가
- admin_rx_usage.html: 도매상별 주문 분기 처리
- 수인/지오영 모두 dry-run 테스트 가능
- 여러 도매상 품목 있을 때 선택 모달
This commit is contained in:
thug0bin
2026-03-06 12:24:15 +09:00
parent 7dda385b7f
commit 50455e63c7
2 changed files with 276 additions and 17 deletions

View File

@@ -409,7 +409,7 @@ def api_quick_submit():
POST /api/order/quick-submit
{
"wholesaler_id": "geoyoung",
"wholesaler_id": "geoyoung" | "sooin",
"items": [...],
"dry_run": true
}
@@ -441,14 +441,226 @@ def api_quick_submit():
if order['wholesaler_id'] == 'geoyoung':
submit_result = submit_geoyoung_order(order, dry_run)
elif order['wholesaler_id'] == 'sooin':
submit_result = submit_sooin_order(order, dry_run)
else:
submit_result = {'success': False, 'error': 'Wholesaler not supported'}
submit_result = {'success': False, 'error': f"Wholesaler {order['wholesaler_id']} not supported"}
submit_result['order_no'] = create_result['order_no']
return jsonify(submit_result)
def submit_sooin_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 sooin_api import get_sooin_session
sooin_session = get_sooin_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 = sooin_session.search_products(kd_code)
matched = None
available_specs = []
spec_stocks = {}
if search_result.get('success'):
for sooin_item in search_result.get('items', []):
s = sooin_item.get('spec', '')
available_specs.append(s)
spec_stocks[s] = sooin_item.get('stock', 0)
# 규격 매칭
if spec in s or s in spec:
if matched is None or sooin_item.get('stock', 0) > matched.get('stock', 0):
matched = sooin_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': 'sooin'
})
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', '')
internal_code = item.get('internal_code')
try:
# internal_code가 없으면 검색해서 찾기
if not internal_code:
search_result = sooin_session.search_products(kd_code)
if search_result.get('success'):
for sooin_item in search_result.get('items', []):
if spec in sooin_item.get('spec', '') or sooin_item.get('spec', '') in spec:
internal_code = sooin_item.get('internal_code')
break
if not internal_code:
raise ValueError(f"내부 코드를 찾을 수 없음: {kd_code} {spec}")
# 장바구니 추가
cart_result = sooin_session.add_to_cart(internal_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': 'sooin',
'internal_code': internal_code
})
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': 'sooin',
'total_items': len(items),
'success_count': success_count,
'failed_count': failed_count,
'results': results,
'note': '실제 주문 시 장바구니에 담김. 수인약품 사이트에서 최종 확정 필요.' 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
# ─────────────────────────────────────────────