fix(baekje): 장바구니 담기 시 internal_code 사용하도록 수정

- kd_code 대신 internal_code로 장바구니 추가
- internal_code 없으면 검색 후 규격 매칭으로 찾기
- 백제 장바구니 담기 정상 작동 확인
This commit is contained in:
thug0bin 2026-03-07 21:29:00 +09:00
parent 232a77006a
commit 0ae4ae66f0
9 changed files with 402 additions and 53 deletions

View File

@ -262,6 +262,148 @@ def api_get_balance():
return jsonify(result)
@baekje_bp.route('/orders/summary-by-kd', methods=['GET'])
def api_baekje_orders_by_kd():
"""
백제약품 주문량 KD코드별 집계 API
GET /api/baekje/orders/summary-by-kd?start_date=2026-03-01&end_date=2026-03-07
Returns:
{
"success": true,
"order_count": 4,
"by_kd_code": {
"670400830": {
"product_name": "레바미피드정",
"spec": "100T",
"boxes": 2,
"units": 200
}
},
"total_products": 15
}
"""
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, product_name: str = '') -> int:
"""
규격에서 수량 추출 (30T 30, 100C 100)
"""
combined = f"{spec} {product_name}"
# D(도즈) 단위는 박스 단위로 계산 (140D → 1)
if re.search(r'\d+\s*D\b', combined, re.IGNORECASE):
return 1
# T/C/P 단위가 붙은 숫자 추출 (예: 14T, 100C, 30P)
qty_match = re.search(r'(\d+)\s*[TCP]\b', combined, re.IGNORECASE)
if qty_match:
return int(qty_match.group(1))
# 없으면 spec의 첫 번째 숫자
if spec:
num_match = re.search(r'(\d+)', spec)
if num_match:
val = int(num_match.group(1))
# mg, ml 같은 용량 단위면 수량 1로 처리
if re.search(r'\d+\s*(mg|ml|g)\b', spec, re.IGNORECASE):
return 1
return val
return 1
try:
session = get_baekje_session()
# 1. 주문 목록 조회
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': {},
'order_count': 0
})
orders = orders_result.get('orders', [])
if not orders:
return jsonify({
'success': True,
'order_count': 0,
'period': {'start': start_date, 'end': end_date},
'by_kd_code': {},
'total_products': 0
})
# 2. 주문 상세 정보 배치 조회 (items 포함)
details_result = session.get_order_details_batch(orders=orders)
if not details_result.get('success'):
logger.warning(f"백제 주문 상세 조회 실패: {details_result.get('error')}")
details = details_result.get('details', {})
# KD코드별 집계
kd_summary = {}
for order_num, detail in details.items():
for item in detail.get('items', []):
# 취소 상태 제외
status = item.get('status', '').strip()
if '취소' in status or '삭제' in status:
continue
# 백제는 kd_code가 insurance_code(BOHUM_CD)에 있음
kd_code = item.get('kd_code', '') or item.get('insurance_code', '')
if not kd_code:
continue
product_name = item.get('product_name', '')
spec = item.get('spec', '')
quantity = item.get('quantity', 0) or item.get('order_qty', 0)
per_unit = parse_spec(spec, product_name)
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
logger.info(f"백제 주문량 집계: {start_date}~{end_date}, {len(orders)}건 주문, {len(kd_summary)}개 품목")
return jsonify({
'success': True,
'order_count': len(orders),
'period': {'start': start_date, 'end': end_date},
'by_kd_code': kd_summary,
'total_products': len(kd_summary)
})
except Exception as e:
logger.error(f"백제 주문량 집계 오류: {e}")
return jsonify({
'success': False,
'error': 'API_ERROR',
'message': str(e),
'by_kd_code': {},
'order_count': 0
}), 500
@baekje_bp.route('/monthly-sales', methods=['GET'])
def api_get_monthly_sales():
"""

36
backend/check_basen.py Normal file
View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
import requests
print("=== 어제 베이슨 주문 (지오영 + 수인) ===\n")
# 지오영
geo = requests.get('http://localhost:7001/api/geoyoung/orders/summary-by-kd?start_date=2026-03-06&end_date=2026-03-06', timeout=120).json()
print("【지오영】")
found = False
for kd, info in geo.get('by_kd_code', {}).items():
if '베이슨' in info['product_name']:
print(f" KD: {kd}")
print(f" 제품명: {info['product_name']}")
print(f" spec: {info['spec']}")
print(f" boxes: {info['boxes']}")
print(f" units: {info['units']}")
found = True
if not found:
print(" (없음)")
print()
# 수인
sooin = requests.get('http://localhost:7001/api/sooin/orders/summary-by-kd?start_date=2026-03-06&end_date=2026-03-06', timeout=120).json()
print("【수인약품】")
found = False
for kd, info in sooin.get('by_kd_code', {}).items():
if '베이슨' in info['product_name']:
print(f" KD: {kd}")
print(f" 제품명: {info['product_name']}")
print(f" spec: {info['spec']}")
print(f" boxes: {info['boxes']}")
print(f" units: {info['units']}")
found = True
if not found:
print(" (없음)")

View File

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
import requests
import sys
sys.path.insert(0, 'c:/Users/청춘약국/source/pharmacy-wholesale-api')
from dotenv import load_dotenv
load_dotenv('c:/Users/청춘약국/source/pharmacy-wholesale-api/.env')
from wholesale import GeoYoungSession
# 지오영 베이슨 검색
print("=== 지오영 베이슨 검색 결과 ===")
res = requests.get('http://localhost:7001/api/geoyoung/stock?keyword=베이슨', timeout=30).json()
for item in res.get('items', []):
internal = item.get('internal_code', '')
spec = item.get('specification', '')
name = item.get('product_name', '')
print(f" 내부: {internal} | spec: {spec:8} | {name}")
print()
# 어제 주문 상세
print("=== 어제 베이슨 주문 상세 ===")
session = GeoYoungSession()
session.login()
result = session.get_order_list('2026-03-06', '2026-03-06')
if result.get('success'):
for order in result.get('orders', []):
for item in order.get('items', []):
name = item.get('product_name', '')
if '베이슨' in name:
internal = item.get('product_code', '')
qty = item.get('quantity', 0)
status = item.get('status', '')
print(f" 주문번호: {order.get('order_num')}")
print(f" 제품명: {name}")
print(f" 내부코드: {internal}")
print(f" 수량: {qty}박스")
print(f" 상태: {status}")
print()

View File

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
import sys
sys.path.insert(0, 'c:/Users/청춘약국/source/pharmacy-wholesale-api')
from dotenv import load_dotenv
load_dotenv('c:/Users/청춘약국/source/pharmacy-wholesale-api/.env')
from wholesale import GeoYoungSession
from bs4 import BeautifulSoup
session = GeoYoungSession()
session.login()
# MyPage HTML 직접 확인
resp = session.session.get(
f"{session.BASE_URL}/MyPage",
params={'dtpFrom': '2026-03-06', 'dtpTo': '2026-03-06'},
timeout=30
)
soup = BeautifulSoup(resp.text, 'html.parser')
table = soup.find('table')
tbody = table.find('tbody') or table
rows = tbody.find_all('tr')
print("=== 베이슨 행 HTML 분석 ===\n")
for row in rows:
cells = row.find_all('td')
if not cells or len(cells) < 10:
continue
name = cells[1].get_text(strip=True)
if '베이슨' not in name:
continue
status = cells[9].get_text(strip=True) if len(cells) > 9 else ''
print(f"제품명: {name}")
print(f"상태: {status}")
# 모든 버튼의 onclick 확인
print("버튼들:")
for cell in cells:
for btn in cell.find_all('button'):
onclick = btn.get('onclick', '')
btn_text = btn.get_text(strip=True)
print(f" [{btn_text}] onclick: {onclick[:80]}...")
print()

View File

@ -1,48 +1,47 @@
# -*- coding: utf-8 -*-
import pyodbc
import sys
sys.path.insert(0, 'c:/Users/청춘약국/source/pharmacy-wholesale-api')
from dotenv import load_dotenv
load_dotenv('c:/Users/청춘약국/source/pharmacy-wholesale-api/.env')
from wholesale import GeoYoungSession
conn_str = (
'DRIVER={ODBC Driver 17 for SQL Server};'
'SERVER=192.168.0.4\\PM2014;'
'DATABASE=PM_DRUG;'
'UID=sa;'
'PWD=tmddls214!%(;'
'TrustServerCertificate=yes;'
'Connection Timeout=10'
)
session = GeoYoungSession()
session.login()
conn = pyodbc.connect(conn_str, timeout=10)
cur = conn.cursor()
# 어제 (3월 6일) 주문 조회
result = session.get_order_list('2026-03-06', '2026-03-06')
# 라식스 약품 정보 조회 (전체 컬럼)
cur.execute("""
SELECT TOP 1 *
FROM CD_GOODS
WHERE DrugCode = '652100200'
""")
row = cur.fetchone()
if row:
columns = [desc[0] for desc in cur.description]
print("=== 라식스 약품 정보 ===")
for i, col in enumerate(columns):
if 'price' in col.lower() or 'cost' in col.lower() or 'amount' in col.lower():
print(f"{col}: {row[i]}")
# 처방전에서 라식스 DRUPRICE 확인
conn2 = pyodbc.connect(conn_str.replace('PM_DRUG', 'PM_PRES'), timeout=10)
cur2 = conn2.cursor()
cur2.execute("""
SELECT TOP 5 DrugCode, QUAN, Days, DRUPRICE
FROM PS_sub_pharm
WHERE DrugCode = '652100200'
ORDER BY Indate DESC
""")
print("\n=== 최근 처방 라식스 DRUPRICE ===")
for row in cur2.fetchall():
print(f"DrugCode: {row.DrugCode}, QUAN: {row.QUAN}, Days: {row.Days}, DRUPRICE: {row.DRUPRICE}")
dose = row.QUAN * row.Days
amount = row.DRUPRICE * row.QUAN * row.Days
print(f" → 투약량: {dose}, 매출액: {amount:,}")
if result.get('success'):
# KD코드 매핑
for order in result.get('orders', []):
items = order.get('items', [])
if items:
session._enrich_kd_codes(items)
print("=== 어제 라식스 주문 상세 ===")
total_boxes = 0
for order in result.get('orders', []):
for item in order.get('items', []):
name = item.get('product_name', '')
if '라식스' in name:
status = item.get('status', '')
qty = item.get('quantity', 0)
spec = item.get('spec', '')
internal = item.get('product_code', '')
kd = item.get('kd_code', '')
order_num = order.get('order_num', '')
print(f" 주문번호: {order_num}")
print(f" 제품명: {name}")
print(f" 내부코드: {internal}")
print(f" KD코드: {kd}")
print(f" spec: {spec}")
print(f" 수량: {qty}박스")
print(f" 상태: {status}")
print()
if '취소' not in status and '삭제' not in status:
total_boxes += qty
print(f"=== 합계 (취소 제외): {total_boxes}박스 ===")
else:
print(f"오류: {result.get('error')}")

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
import requests
res = requests.get('http://localhost:7001/api/geoyoung/stock?keyword=라식스', timeout=30).json()
print("=== 지오영 라식스 검색 ===")
for item in res.get('items', []):
internal = item.get('internal_code', '')
spec = item.get('specification', '')
name = item.get('product_name', '')
print(f" 내부: {internal} | spec: {spec} | {name}")

View File

@ -898,12 +898,45 @@ def submit_baekje_order(order: dict, dry_run: bool, cart_only: bool = True) -> d
# ─────────────────────────────────────────
for item in items:
kd_code = item.get('kd_code') or item.get('drug_code')
internal_code = item.get('internal_code') # 프론트엔드에서 전달된 internal_code
order_qty = item['order_qty']
spec = item.get('specification', '')
cart_result = {}
# 🔍 디버그: 백제 주문 파라미터 확인
logger.info(f"[BAEKJE DEBUG] kd_code={kd_code}, internal_code={internal_code}, qty={order_qty}, spec={spec}")
logger.info(f"[BAEKJE DEBUG] full item: {item}")
try:
# 장바구니 추가
cart_result = baekje_session.add_to_cart(kd_code, order_qty)
if internal_code:
# internal_code가 있으면 바로 장바구니 추가!
logger.info(f"[BAEKJE DEBUG] Using internal_code directly: {internal_code}")
cart_result = baekje_session.add_to_cart(internal_code, order_qty)
logger.info(f"[BAEKJE DEBUG] add_to_cart result: {cart_result}")
else:
# internal_code가 없으면 검색 후 장바구니 추가
logger.info(f"[BAEKJE DEBUG] No internal_code, searching by kd_code={kd_code}")
search_result = baekje_session.search_products(kd_code)
if search_result.get('success') and search_result.get('items'):
# 규격 매칭 (재고 있는 것 우선)
matched_item = None
for baekje_item in search_result.get('items', []):
item_spec = baekje_item.get('spec', '')
# 규격이 지정되어 있으면 매칭, 없으면 첫번째 재고 있는 것
if not spec or spec in item_spec or item_spec in spec:
if matched_item is None or baekje_item.get('stock', 0) > matched_item.get('stock', 0):
matched_item = baekje_item
if matched_item:
found_internal_code = matched_item.get('internal_code')
logger.info(f"[BAEKJE DEBUG] Found internal_code via search: {found_internal_code}")
cart_result = baekje_session.add_to_cart(found_internal_code, order_qty)
internal_code = found_internal_code # 컨텍스트 저장용
else:
cart_result = {'success': False, 'error': 'NO_MATCHING_SPEC', 'message': f'규격 {spec} 미발견'}
else:
cart_result = {'success': False, 'error': 'PRODUCT_NOT_FOUND', 'message': '제품 검색 결과 없음'}
if cart_result.get('success'):
status = 'success'
@ -921,6 +954,7 @@ def submit_baekje_order(order: dict, dry_run: bool, cart_only: bool = True) -> d
result_code = 'ERROR'
result_message = str(e)
failed_count += 1
logger.error(f"[BAEKJE DEBUG] Exception: {e}")
update_item_result(item['id'], status, result_code, result_message)
@ -932,7 +966,8 @@ def submit_baekje_order(order: dict, dry_run: bool, cart_only: bool = True) -> d
'ordered_spec': spec,
'ordered_qty': order_qty,
'selection_reason': 'user_order',
'wholesaler_id': 'baekje'
'wholesaler_id': 'baekje',
'internal_code': internal_code
})
results.append({
@ -943,7 +978,8 @@ def submit_baekje_order(order: dict, dry_run: bool, cart_only: bool = True) -> d
'order_qty': order_qty,
'status': status,
'result_code': result_code,
'result_message': result_message
'result_message': result_message,
'internal_code': internal_code
})
# cart_only=False면 주문 확정까지 진행

View File

@ -907,7 +907,7 @@
loadOrderData(); // 수인약품 주문량 로드
});
// ──────────────── 도매상 주문량 조회 (지오영 + 수인 합산) ────────────────
// ──────────────── 도매상 주문량 조회 (지오영 + 수인 + 백제 합산) ────────────────
async function loadOrderData() {
const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value;
@ -916,10 +916,11 @@
orderDataByKd = {};
try {
// 지오영 + 수인 병렬 조회
const [geoRes, sooinRes] = await Promise.all([
// 지오영 + 수인 + 백제 병렬 조회
const [geoRes, sooinRes, baekjeRes] = await Promise.all([
fetch(`/api/geoyoung/orders/summary-by-kd?start_date=${startDate}&end_date=${endDate}`).then(r => r.json()).catch(() => ({ success: false })),
fetch(`/api/sooin/orders/summary-by-kd?start_date=${startDate}&end_date=${endDate}`).then(r => r.json()).catch(() => ({ success: false }))
fetch(`/api/sooin/orders/summary-by-kd?start_date=${startDate}&end_date=${endDate}`).then(r => r.json()).catch(() => ({ success: false })),
fetch(`/api/baekje/orders/summary-by-kd?start_date=${startDate}&end_date=${endDate}`).then(r => r.json()).catch(() => ({ success: false }))
]);
let totalOrders = 0;
@ -952,7 +953,21 @@
console.log('💜 수인 주문량:', Object.keys(sooinRes.by_kd_code).length, '품목,', sooinRes.order_count, '건');
}
console.log('📦 합산 주문량:', Object.keys(orderDataByKd).length, '품목,', totalOrders, '건 주문');
// 백제 데이터 합산
if (baekjeRes.success && baekjeRes.by_kd_code) {
for (const [kd, data] of Object.entries(baekjeRes.by_kd_code)) {
if (!orderDataByKd[kd]) {
orderDataByKd[kd] = { product_name: data.product_name, spec: data.spec, boxes: 0, units: 0, sources: [] };
}
orderDataByKd[kd].boxes += data.boxes || 0;
orderDataByKd[kd].units += data.units || 0;
orderDataByKd[kd].sources.push('백제');
}
totalOrders += baekjeRes.order_count || 0;
console.log('💉 백제 주문량:', Object.keys(baekjeRes.by_kd_code).length, '품목,', baekjeRes.order_count, '건');
}
console.log('📦 3사 합산 주문량:', Object.keys(orderDataByKd).length, '품목,', totalOrders, '건 주문');
} catch(err) {
console.warn('주문량 조회 실패:', err);

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
import requests
print('=== 주문량 API 테스트 (지오영 + 수인 + 백제) ===')
date = '2026-03-07'
# 지오영
geo = requests.get(f'http://localhost:7001/api/geoyoung/orders/summary-by-kd?start_date={date}&end_date={date}', timeout=60).json()
geo_count = len(geo.get('by_kd_code', {}))
print(f'지오영: {"OK" if geo.get("success") else "FAIL"} - {geo_count}개 품목')
# 수인
sooin = requests.get(f'http://localhost:7001/api/sooin/orders/summary-by-kd?start_date={date}&end_date={date}', timeout=60).json()
sooin_count = len(sooin.get('by_kd_code', {}))
print(f'수인: {"OK" if sooin.get("success") else "FAIL"} - {sooin_count}개 품목')
# 백제
baekje = requests.get(f'http://localhost:7001/api/baekje/orders/summary-by-kd?start_date={date}&end_date={date}', timeout=60).json()
baekje_count = len(baekje.get('by_kd_code', {}))
print(f'백제: {"OK" if baekje.get("success") else "FAIL"} - {baekje_count}개 품목')
if baekje.get('message'):
print(f' 메시지: {baekje.get("message")}')
print()
print(f'총 품목: {geo_count + sooin_count + baekje_count}')