diff --git a/backend/order_api.py b/backend/order_api.py index a8ad0af..58fef7e 100644 --- a/backend/order_api.py +++ b/backend/order_api.py @@ -152,14 +152,21 @@ def api_submit_order(): return jsonify(result) -def submit_geoyoung_order(order: dict, dry_run: bool) -> dict: - """지오영 주문 제출""" +def submit_geoyoung_order(order: dict, dry_run: bool, cart_only: bool = True) -> dict: + """ + 지오영 주문 제출 + + Args: + order: 주문 정보 + dry_run: True=시뮬레이션만, False=실제 주문 + cart_only: True=장바구니만, False=주문 확정까지 + """ order_id = order['id'] items = order['items'] # 상태 업데이트 update_order_status(order_id, 'pending', - f'주문 제출 시작 (dry_run={dry_run})') + f'주문 제출 시작 (dry_run={dry_run}, cart_only={cart_only})') results = [] success_count = 0 @@ -286,20 +293,22 @@ def submit_geoyoung_order(order: dict, dry_run: bool) -> dict: spec = item.get('specification', '') try: - # 지오영 주문 실행 (빠른 API - 장바구니+확정) + # 지오영 주문 실행 + # cart_only=True: 장바구니만 (auto_confirm=False) + # cart_only=False: 주문 확정까지 (auto_confirm=True) result = geo_session.full_order( kd_code=kd_code, quantity=order_qty, specification=spec if spec else None, check_stock=True, - auto_confirm=True, + auto_confirm=not cart_only, # cart_only면 확정 안함 memo=f"자동주문 - {item.get('product_name', '')}" ) if result.get('success'): status = 'success' - result_code = 'OK' - result_message = result.get('message', '주문 완료') + result_code = 'CART_ADDED' if cart_only else 'OK' + result_message = '장바구니 추가 완료' if cart_only else result.get('message', '주문 완료') success_count += 1 else: status = 'failed' @@ -338,25 +347,41 @@ def submit_geoyoung_order(order: dict, dry_run: bool) -> dict: }) # 주문 상태 업데이트 - if failed_count == 0: - update_order_status(order_id, 'submitted', - f'주문 제출 완료: {success_count}개 품목') - elif success_count == 0: - update_order_status(order_id, 'failed', - f'주문 실패: {failed_count}개 품목') + if cart_only: + # 장바구니만 담은 경우 + if failed_count == 0: + update_order_status(order_id, 'pending', + f'지오영 장바구니 추가 완료: {success_count}개 (사이트에서 확정 필요)') + elif success_count == 0: + update_order_status(order_id, 'failed', + f'장바구니 추가 실패: {failed_count}개 품목') + else: + update_order_status(order_id, 'partial', + f'부분 성공: {success_count}개 장바구니, {failed_count}개 실패') else: - update_order_status(order_id, 'partial', - f'부분 주문: {success_count}개 성공, {failed_count}개 실패') + # 실제 주문까지 한 경우 + if failed_count == 0: + update_order_status(order_id, 'submitted', + f'주문 제출 완료: {success_count}개 품목') + elif success_count == 0: + update_order_status(order_id, 'failed', + f'주문 실패: {failed_count}개 품목') + else: + update_order_status(order_id, 'partial', + f'부분 주문: {success_count}개 성공, {failed_count}개 실패') return { 'success': True, 'dry_run': dry_run, + 'cart_only': cart_only, 'order_id': order_id, 'order_no': order['order_no'], + 'wholesaler': 'geoyoung', 'total_items': len(items), 'success_count': success_count, 'failed_count': failed_count, - 'results': results + 'results': results, + 'note': '지오영 장바구니에 담김. 지오영 사이트에서 최종 확정 필요.' if cart_only else None } except Exception as e: @@ -409,9 +434,10 @@ def api_quick_submit(): POST /api/order/quick-submit { - "wholesaler_id": "geoyoung" | "sooin", + "wholesaler_id": "geoyoung" | "sooin" | "baekje", "items": [...], - "dry_run": true + "dry_run": true, + "cart_only": true // true=장바구니만, false=실제 주문까지 } """ data = request.get_json() @@ -438,13 +464,14 @@ def api_quick_submit(): # 3. 주문 제출 dry_run = data.get('dry_run', True) + cart_only = data.get('cart_only', True) # 기본값: 장바구니만 if order['wholesaler_id'] == 'geoyoung': - submit_result = submit_geoyoung_order(order, dry_run) + submit_result = submit_geoyoung_order(order, dry_run, cart_only=cart_only) elif order['wholesaler_id'] == 'sooin': - submit_result = submit_sooin_order(order, dry_run) + submit_result = submit_sooin_order(order, dry_run, cart_only=cart_only) elif order['wholesaler_id'] == 'baekje': - submit_result = submit_baekje_order(order, dry_run) + submit_result = submit_baekje_order(order, dry_run, cart_only=cart_only) else: submit_result = {'success': False, 'error': f"Wholesaler {order['wholesaler_id']} not supported"} @@ -453,14 +480,21 @@ def api_quick_submit(): return jsonify(submit_result) -def submit_sooin_order(order: dict, dry_run: bool) -> dict: - """수인약품 주문 제출""" +def submit_sooin_order(order: dict, dry_run: bool, cart_only: bool = True) -> dict: + """ + 수인약품 주문 제출 + + Args: + order: 주문 정보 + dry_run: True=시뮬레이션만, False=실제 주문 + cart_only: True=장바구니만, False=주문 확정까지 + """ order_id = order['id'] items = order['items'] # 상태 업데이트 update_order_status(order_id, 'pending', - f'수인 주문 시작 (dry_run={dry_run})') + f'수인 주문 시작 (dry_run={dry_run}, cart_only={cart_only})') results = [] success_count = 0 @@ -572,23 +606,15 @@ def submit_sooin_order(order: dict, dry_run: bool) -> dict: 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) + # quick_order 사용 (검색 → 가격/재고 정보 포함하여 장바구니 추가) + cart_result = sooin_session.quick_order( + kd_code=kd_code, + quantity=order_qty, + spec=spec if spec else None, + check_stock=False # 재고 체크는 이미 테스트에서 했으므로 스킵 + ) if cart_result.get('success'): status = 'success' @@ -609,6 +635,9 @@ def submit_sooin_order(order: dict, dry_run: bool) -> dict: update_item_result(item['id'], status, result_code, result_message) + # quick_order 결과에서 internal_code 가져오기 + internal_code = cart_result.get('product', {}).get('internal_code') if cart_result.get('success') else None + save_order_context(item['id'], { 'drug_code': item['drug_code'], 'product_name': item['product_name'], @@ -629,20 +658,57 @@ def submit_sooin_order(order: dict, dry_run: bool) -> dict: 'order_qty': order_qty, 'status': status, 'result_code': result_code, - 'result_message': result_message + 'result_message': result_message, + 'internal_code': internal_code # 선별 주문용 }) - # 주문 확정은 별도로 (장바구니에 담기만 한 상태) - if success_count > 0: + # cart_only=False면 주문 확정까지 진행 (선별 주문!) + if not cart_only and success_count > 0: + try: + # 이번에 담은 품목의 internal_code만 수집 + ordered_codes = [r['internal_code'] for r in results + if r['status'] == 'success' and r.get('internal_code')] + + if ordered_codes: + # 선별 주문: 기존 품목은 건드리지 않고, 이번에 담은 것만 주문 + confirm_result = sooin_session.submit_order_selective(ordered_codes) + + if confirm_result.get('success'): + restored_info = f", 기존 {confirm_result.get('restored_count', 0)}개 복원" if confirm_result.get('restored_count', 0) > 0 else "" + update_order_status(order_id, 'submitted', + f'수인 주문 확정 완료: {success_count}개{restored_info}') + # 결과 메시지 업데이트 + for r in results: + if r['status'] == 'success': + r['result_code'] = 'OK' + r['result_message'] = '주문 확정 완료' + else: + update_order_status(order_id, 'partial', + f'수인 장바구니 담김, 확정 실패: {confirm_result.get("error", "알 수 없는 오류")}') + else: + update_order_status(order_id, 'partial', + f'수인 장바구니 담김, internal_code 없음') + except Exception as e: + logger.error(f"수인 주문 확정 오류: {e}") + update_order_status(order_id, 'partial', + f'수인 장바구니 담김, 확정 중 오류: {str(e)}') + elif success_count > 0: update_order_status(order_id, 'pending', f'수인 장바구니 추가 완료: {success_count}개 (확정 필요)') else: update_order_status(order_id, 'failed', f'수인 주문 실패: {failed_count}개') + # 응답 생성 + if cart_only: + note = '수인약품 장바구니에 담김. 사이트에서 최종 확정 필요.' + else: + note = None + return { 'success': True, 'dry_run': dry_run, + 'cart_only': cart_only, 'order_id': order_id, 'order_no': order['order_no'], 'wholesaler': 'sooin', @@ -650,7 +716,7 @@ def submit_sooin_order(order: dict, dry_run: bool) -> dict: 'success_count': success_count, 'failed_count': failed_count, 'results': results, - 'note': '실제 주문 시 장바구니에 담김. 수인약품 사이트에서 최종 확정 필요.' if not dry_run else None + 'note': note if not dry_run else None } except Exception as e: @@ -663,14 +729,21 @@ def submit_sooin_order(order: dict, dry_run: bool) -> dict: } -def submit_baekje_order(order: dict, dry_run: bool) -> dict: - """백제약품 주문 제출""" +def submit_baekje_order(order: dict, dry_run: bool, cart_only: bool = True) -> dict: + """ + 백제약품 주문 제출 + + Args: + order: 주문 정보 + dry_run: True=시뮬레이션만, False=실제 주문 + cart_only: True=장바구니만, False=주문 확정까지 + """ order_id = order['id'] items = order['items'] # 상태 업데이트 update_order_status(order_id, 'pending', - f'백제약품 주문 시작 (dry_run={dry_run})') + f'백제약품 주문 시작 (dry_run={dry_run}, cart_only={cart_only})') results = [] success_count = 0 @@ -828,17 +901,42 @@ def submit_baekje_order(order: dict, dry_run: bool) -> dict: 'result_message': result_message }) - # 상태 업데이트 - if success_count > 0: + # cart_only=False면 주문 확정까지 진행 + if not cart_only and success_count > 0: + try: + confirm_result = baekje_session.submit_order() + if confirm_result.get('success'): + update_order_status(order_id, 'submitted', + f'백제 주문 확정 완료: {success_count}개') + # 결과 메시지 업데이트 + for r in results: + if r['status'] == 'success': + r['result_code'] = 'OK' + r['result_message'] = '주문 확정 완료' + else: + update_order_status(order_id, 'partial', + f'백제 장바구니 담김, 확정 실패: {confirm_result.get("error", "알 수 없는 오류")}') + except Exception as e: + logger.error(f"백제 주문 확정 오류: {e}") + update_order_status(order_id, 'partial', + f'백제 장바구니 담김, 확정 중 오류: {str(e)}') + elif success_count > 0: update_order_status(order_id, 'pending', f'백제 장바구니 추가 완료: {success_count}개 (확정 필요)') else: update_order_status(order_id, 'failed', f'백제 주문 실패: {failed_count}개') + # 응답 생성 + if cart_only: + note = '백제약품 장바구니에 담김. 백제몰(ibjp.co.kr)에서 최종 확정 필요.' + else: + note = None + return { 'success': True, 'dry_run': dry_run, + 'cart_only': cart_only, 'order_id': order_id, 'order_no': order['order_no'], 'wholesaler': 'baekje', @@ -846,7 +944,7 @@ def submit_baekje_order(order: dict, dry_run: bool) -> dict: 'success_count': success_count, 'failed_count': failed_count, 'results': results, - 'note': '실제 주문 시 장바구니에 담김. 백제몰(ibjp.co.kr)에서 최종 확정 필요.' if not dry_run else None + 'note': note if not dry_run else None } except Exception as e: diff --git a/backend/templates/admin_rx_usage.html b/backend/templates/admin_rx_usage.html index d8a58dd..6ef66df 100644 --- a/backend/templates/admin_rx_usage.html +++ b/backend/templates/admin_rx_usage.html @@ -528,6 +528,25 @@ color: var(--text-muted); margin-top: 4px; } + .cart-item-actions { + display: flex; + gap: 6px; + align-items: center; + } + .cart-item-order { + background: rgba(16, 185, 129, 0.2); + border: none; + color: var(--accent-emerald); + padding: 4px 8px; + border-radius: 6px; + cursor: pointer; + font-size: 11px; + font-weight: 600; + white-space: nowrap; + } + .cart-item-order:hover { + background: rgba(16, 185, 129, 0.4); + } .cart-item-remove { background: rgba(244, 63, 94, 0.2); border: none; @@ -1049,7 +1068,10 @@
+ 장바구니에 ${wsIds.length}개 도매상의 품목이 있습니다. +
+ `; + + // 각 도매상별 품목 표시 wsIds.forEach(wsId => { const ws = WHOLESALERS[wsId]; - msg += `${ws.icon} ${ws.name}: ${itemsByWholesaler[wsId].length}개\n`; + const items = itemsByWholesaler[wsId]; + html += ` +- 0개 품목을 지오영에 주문합니다. + 0개 품목을 지오영 장바구니에 담습니다. +
++ ⚠️ 장바구니 담기만 진행됩니다. 도매상 사이트에서 최종 확정이 필요합니다.
| 품목명 | 규격 | 수량 |
|---|