feat(order): 지오영/수인 선택적 주문 + 장바구니 보존 기능

- internal_code DB 저장 → 프론트에서 선택한 제품 그대로 주문
- 기존 장바구니 백업/복구로 사용자 장바구니 보존
- 수인약품 submit_order() 수정 (체크박스 제외 방식)
- 테스트 파일 정리 및 문서 추가
This commit is contained in:
thug0bin
2026-03-06 23:26:44 +09:00
parent f48e657e12
commit a672c7a2a0
79 changed files with 4851 additions and 2672 deletions

16
backend/analyze_bag.py Normal file
View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import SooinSession
s = SooinSession()
s.login()
# Bag.asp HTML 가져오기
resp = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
# 파일로 저장
with open('bag_page.html', 'w', encoding='utf-8') as f:
f.write(resp.text)
print('bag_page.html 저장됨')
print(f'응답 길이: {len(resp.text)}')

View File

@@ -99,63 +99,7 @@
<input type="hidden" name="pDate" id="pDate" value=""/>
</fieldset>
<fieldset class="list">
<legend>장바구니</legend>
<table class="bag_list" summary="스크롤링을 위해 고정시킬 테이블 제목">
<caption>장바구니 리스트</caption>
<colgroup>
<col width="30" />
<col width="*" />
<col width="35" />
<col width="77" />
</colgroup>
<thead>
<tr>
<th scope="col" class="title1 first">건별취소</th>
<th scope="col" class="title2">제품명</th>
<th scope="col" class="title3">수량</th>
<th scope="col" class="title4">금액</th>
</tr>
</thead>
</table>
<div id="bag_view"
style='height:375px;'
> <!--닫는 태그-->
</fieldset>
@@ -168,7 +112,7 @@
<caption>장바구니 리스트</caption>
<col width="*" />
<colgroup>
@@ -177,15 +121,15 @@
<col width="*" />
<col width="35" />
<thead style="display:none;">
<col width="77" />
</colgroup>
<thead>
<tr>

82
backend/capture_order.py Normal file
View File

@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
"""
네트워크 캡처용 - 약사님이 직접 주문 버튼 클릭
"""
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import SooinSession
from playwright.sync_api import sync_playwright
import time
s = SooinSession()
print('로그인...')
s.login()
# 장바구니에 코자정 담기
print('\n코자정 검색...')
result = s.search_products('코자정 50mg PTP')
product = None
for item in result.get('items', []):
if 'PTP' in item['name']:
product = item
break
if product:
print(f"제품: {product['name']} - {product['price']:,}")
s.add_to_cart(product['internal_code'], qty=1,
price=product['price'], stock=product['stock'])
print('장바구니에 담음!')
else:
print('제품 못 찾음')
# 장바구니 확인
cart = s.get_cart()
print(f"\n장바구니: {cart['total_items']}개, {cart['total_amount']:,}")
print('\n' + '='*50)
print('브라우저 열기 + 네트워크 캡처 시작')
print('='*50)
with sync_playwright() as p:
browser = p.chromium.launch(headless=False) # 브라우저 보임
context = browser.new_context()
# 세션 쿠키 복사
for c in s.session.cookies:
context.add_cookies([{
'name': c.name,
'value': c.value,
'domain': c.domain or 'sooinpharm.co.kr',
'path': c.path or '/'
}])
page = context.new_page()
# 네트워크 요청 캡처
def on_request(request):
if 'BagOrder' in request.url and request.method == 'POST':
print('\n' + '='*50)
print('🎯 POST 요청 캡처!')
print('='*50)
print(f'URL: {request.url}')
print(f'\nPOST 데이터:')
data = request.post_data or ''
# 파라미터별로 출력
for param in data.split('&'):
if '=' in param:
key, val = param.split('=', 1)
print(f' {key}: {val[:50]}')
print('='*50)
page.on('request', on_request)
# 주문 페이지로 이동
page.goto('http://sooinpharm.co.kr/Service/Order/Order.asp')
print('\n✅ 브라우저 준비 완료!')
print('👆 주문전송 버튼을 클릭해주세요!')
print('\n(Enter 누르면 브라우저 닫힘)')
input()
browser.close()
print('\n완료!')

79
backend/capture_order2.py Normal file
View File

@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
"""
네트워크 캡처 v2 - 새로고침 후에도 캡처
"""
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import SooinSession
from playwright.sync_api import sync_playwright
import time
s = SooinSession()
print('로그인...')
s.login()
# 먼저 장바구니 비우기
s.clear_cart()
# 코자정 담기
print('코자정 검색...')
result = s.search_products('코자정')
product = result['items'][0] if result.get('items') else None
if product:
print(f"제품: {product['name']} - {product['price']:,}")
s.add_to_cart(product['internal_code'], qty=1,
price=product['price'], stock=product['stock'])
print('장바구니에 담음!')
cart = s.get_cart()
print(f"장바구니: {cart['total_items']}")
print('\n브라우저 열기...')
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context()
# 쿠키 복사
for c in s.session.cookies:
context.add_cookies([{
'name': c.name,
'value': c.value,
'domain': c.domain or 'sooinpharm.co.kr',
'path': c.path or '/'
}])
page = context.new_page()
# 모든 요청 캡처 (지속적)
captured = []
def capture(request):
if 'BagOrder' in request.url and request.method == 'POST':
data = request.post_data or ''
captured.append(data)
print('\n' + '='*60)
print('🎯 POST 캡처!')
print('='*60)
for param in data.split('&')[:30]: # 주요 파라미터만
if '=' in param:
k, v = param.split('=', 1)
if v: # 값이 있는 것만
print(f' {k}: {v[:60]}')
print('='*60)
context.on('request', capture) # context 레벨에서 캡처
page.goto('http://sooinpharm.co.kr/Service/Order/Order.asp')
print('\n✅ 준비 완료!')
print('👆 F5로 새로고침 후 주문전송 버튼 클릭!')
print('\n(Enter 누르면 종료)')
input()
# 캡처된 데이터 파일로 저장
if captured:
with open('captured_post.txt', 'w', encoding='utf-8') as f:
f.write(captured[0])
print('\n📁 captured_post.txt 저장됨')
browser.close()

8
backend/check_cart.py Normal file
View File

@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import SooinSession
s = SooinSession()
s.login()
cart = s.get_cart()
print(f"장바구니: {cart['total_items']}개, {cart['total_amount']:,}")

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import SooinSession
s = SooinSession()
s.login()
cart = s.get_cart()
print(f'성공: {cart["success"]}')
print(f'품목 수: {cart["total_items"]}')
print(f'총액: {cart["total_amount"]:,}')
print()
if cart['items']:
print('=== 장바구니 품목 ===')
for item in cart['items']:
status = '' if item.get('active') else '❌취소'
name = item['product_name'][:30]
print(f"{status} {name:30} x{item['quantity']} = {item['amount']:,}")
else:
print('🛒 장바구니 비어있음')

BIN
backend/geo_cart_before.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -295,14 +295,22 @@ def submit_geoyoung_order(order: dict, dry_run: bool, cart_only: bool = True) ->
item_internal_code = item.get('internal_code') # 프론트에서 이미 선택한 품목
result = {}
# 🔍 디버그 로그
logger.info(f"[GEO DEBUG] item keys: {list(item.keys())}")
logger.info(f"[GEO DEBUG] kd_code={kd_code}, internal_code={item_internal_code}, qty={order_qty}, spec={spec}")
logger.info(f"[GEO DEBUG] full item: {item}")
try:
if item_internal_code:
# internal_code가 있으면 검색 없이 바로 장바구니 추가!
logger.info(f"[GEO DEBUG] Using internal_code directly: {item_internal_code}")
result = geo_session.add_to_cart(item_internal_code, order_qty)
logger.info(f"[GEO DEBUG] add_to_cart result: {result}")
if result.get('success'):
result['product'] = {'internal_code': item_internal_code, 'name': item.get('product_name', '')}
else:
# internal_code 없으면 검색 후 장바구니 추가
logger.info(f"[GEO DEBUG] No internal_code, using full_order with kd_code={kd_code}")
result = geo_session.full_order(
kd_code=kd_code,
quantity=order_qty,
@@ -311,6 +319,7 @@ def submit_geoyoung_order(order: dict, dry_run: bool, cart_only: bool = True) ->
auto_confirm=False,
memo=f"자동주문 - {item.get('product_name', '')}"
)
logger.info(f"[GEO DEBUG] full_order result: {result}")
if result.get('success'):
status = 'success'
@@ -366,6 +375,11 @@ def submit_geoyoung_order(order: dict, dry_run: bool, cart_only: bool = True) ->
ordered_codes = [r['internal_code'] for r in results
if r['status'] == 'success' and r.get('internal_code')]
# 🔧 디버그: 선별 주문 전 상세 로그
logger.info(f"[GEO DEBUG] 선별 주문 시작")
logger.info(f"[GEO DEBUG] ordered_codes: {ordered_codes}")
logger.info(f"[GEO DEBUG] results: {[(r.get('product_name', '')[:20], r.get('internal_code')) for r in results if r['status'] == 'success']}")
if ordered_codes:
# 선별 주문: 기존 품목은 건드리지 않고, 이번에 담은 것만 주문
confirm_result = geo_session.submit_order_selective(ordered_codes)

View File

@@ -105,6 +105,7 @@ def init_db():
-- 약품 정보
drug_code TEXT NOT NULL, -- PIT3000 약품코드
kd_code TEXT, -- 보험코드 (지오영 검색용)
internal_code TEXT, -- 🔧 도매상 내부 코드 (장바구니 직접 추가용!)
product_name TEXT NOT NULL,
manufacturer TEXT,
@@ -372,14 +373,15 @@ def create_order(wholesaler_id: str, items: List[Dict],
cursor.execute('''
INSERT INTO order_items (
order_id, drug_code, kd_code, product_name, manufacturer,
order_id, drug_code, kd_code, internal_code, product_name, manufacturer,
specification, unit_qty, order_qty, total_dose,
usage_qty, current_stock, status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending')
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending')
''', (
order_id,
item.get('drug_code'),
item.get('kd_code'),
item.get('internal_code'), # 🔧 도매상 내부 코드 저장!
item.get('product_name'),
item.get('manufacturer'),
item.get('specification'),

View File

@@ -1464,6 +1464,10 @@
showToast(`📤 ${item.product_name} 주문 중...`, 'info');
// 🔍 디버그: 장바구니 아이템 확인
console.log('[DEBUG] orderSingleItem - cart item:', JSON.stringify(item, null, 2));
console.log('[DEBUG] internal_code:', item.internal_code);
try {
const payload = {
wholesaler_id: wholesaler,
@@ -1999,6 +2003,11 @@
baekje_code: wholesaler === 'baekje' ? item.internal_code : null
};
// 🔍 디버그: 장바구니 추가 시 internal_code 확인
console.log('[DEBUG] addToCartFromWholesale');
console.log('[DEBUG] wholesaler item:', JSON.stringify(item, null, 2));
console.log('[DEBUG] cartItem internal_code:', cartItem.internal_code);
// 기존 항목 체크 (같은 도매상 + 같은 규격)
const existing = cart.find(c =>
c.drug_code === currentWholesaleItem.drug_code &&

60
backend/test_api_debug.py Normal file
View File

@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
"""API 직접 테스트 - 디버그용"""
import requests
import json
# 지오영에서 실제 품목 검색해서 internal_code 얻기
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import GeoYoungSession
g = GeoYoungSession()
g.login()
# 재고 있는 품목 검색
r = g.search_products('라식스')
if r.get('items'):
item = r['items'][0]
print("="*60)
print("검색된 품목:")
print(f" name: {item['name']}")
print(f" internal_code: {item['internal_code']}")
print(f" stock: {item['stock']}")
print(f" price: {item['price']}")
print("="*60)
# API 호출
payload = {
"wholesaler_id": "geoyoung",
"items": [{
"drug_code": "652100200",
"kd_code": "라식스",
"internal_code": item['internal_code'], # 검색된 internal_code 사용
"product_name": item['name'],
"manufacturer": "한독",
"specification": item.get('spec', ''),
"order_qty": 1,
"usage_qty": 100,
"current_stock": 0
}],
"reference_period": "2026-02-01~2026-03-07",
"dry_run": False,
"cart_only": False
}
print("\n" + "="*60)
print("API 요청:")
print(json.dumps(payload, ensure_ascii=False, indent=2))
print("="*60)
response = requests.post(
'http://localhost:7001/api/order/quick-submit',
json=payload,
timeout=60
)
print("\n" + "="*60)
print(f"응답 (status: {response.status_code}):")
print(json.dumps(response.json(), ensure_ascii=False, indent=2))
print("="*60)
else:
print("품목을 찾을 수 없습니다")

View File

@@ -1,101 +0,0 @@
# -*- coding: utf-8 -*-
"""백제약품 주문 원장 API 분석"""
import json
import requests
from datetime import datetime, timedelta
import calendar
# 저장된 토큰 로드
TOKEN_FILE = r'c:\Users\청춘약국\source\pharmacy-wholesale-api\.baekje_token.json'
with open(TOKEN_FILE, 'r', encoding='utf-8') as f:
token_data = json.load(f)
token = token_data['token']
cust_cd = token_data['cust_cd']
print(f"Token expires: {datetime.fromtimestamp(token_data['expires'])}")
print(f"Customer code: {cust_cd}")
# API 세션 설정
session = requests.Session()
session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
'Origin': 'https://ibjp.co.kr',
'Referer': 'https://ibjp.co.kr/',
'Authorization': f'Bearer {token}'
})
API_URL = "https://www.ibjp.co.kr"
# 1. 주문 원장 API 시도 - 다양한 엔드포인트
endpoints = [
'/ordLedger/listSearch',
'/ordLedger/list',
'/ord/ledgerList',
'/ord/ledgerSearch',
'/cust/ordLedger',
'/custOrd/ledgerList',
'/ordHist/listSearch',
'/ordHist/list',
]
# 날짜 설정 (이번 달)
today = datetime.now()
year = today.year
month = today.month
_, last_day = calendar.monthrange(year, month)
from_date = f"{year}{month:02d}01"
to_date = f"{year}{month:02d}{last_day:02d}"
print(f"\n조회 기간: {from_date} ~ {to_date}")
print("\n=== API 엔드포인트 탐색 ===\n")
params = {
'custCd': cust_cd,
'startDt': from_date,
'endDt': to_date,
'stDate': from_date,
'edDate': to_date,
'year': str(year),
'month': f"{month:02d}",
}
for endpoint in endpoints:
try:
# GET 시도
resp = session.get(f"{API_URL}{endpoint}", params=params, timeout=10)
print(f"GET {endpoint}: {resp.status_code}")
if resp.status_code == 200:
try:
data = resp.json()
print(f" -> JSON Response (first 500 chars): {str(data)[:500]}")
except:
print(f" -> Text (first 200 chars): {resp.text[:200]}")
except Exception as e:
print(f"GET {endpoint}: Error - {e}")
try:
# POST 시도
resp = session.post(f"{API_URL}{endpoint}", json=params, timeout=10)
print(f"POST {endpoint}: {resp.status_code}")
if resp.status_code == 200:
try:
data = resp.json()
print(f" -> JSON Response (first 500 chars): {str(data)[:500]}")
except:
print(f" -> Text (first 200 chars): {resp.text[:200]}")
except Exception as e:
print(f"POST {endpoint}: Error - {e}")
# 2. 이미 알려진 API로 데이터 확인
print("\n=== 알려진 API 테스트 ===\n")
# 월간 잔고 조회 (이미 있는 함수에서 사용)
resp = session.get(f"{API_URL}/custMonth/listSearch", params={'custCd': cust_cd, 'year': str(year), 'endDt': to_date}, timeout=10)
print(f"custMonth/listSearch: {resp.status_code}")
if resp.status_code == 200:
data = resp.json()
print(f" -> {json.dumps(data, ensure_ascii=False, indent=2)[:1500]}")

View File

@@ -1,126 +0,0 @@
# -*- coding: utf-8 -*-
"""백제약품 주문 원장 API 분석 - 상세 탐색"""
import json
import requests
from datetime import datetime
import calendar
# 저장된 토큰 로드
TOKEN_FILE = r'c:\Users\청춘약국\source\pharmacy-wholesale-api\.baekje_token.json'
with open(TOKEN_FILE, 'r', encoding='utf-8') as f:
token_data = json.load(f)
token = token_data['token']
cust_cd = token_data['cust_cd']
# API 세션 설정
session = requests.Session()
session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
'Origin': 'https://ibjp.co.kr',
'Referer': 'https://ibjp.co.kr/',
'Authorization': f'Bearer {token}'
})
API_URL = "https://www.ibjp.co.kr"
today = datetime.now()
year = today.year
month = today.month
_, last_day = calendar.monthrange(year, month)
print("=== 주문 원장 API 탐색 (다양한 파라미터) ===\n")
# 날짜 형식 변형
date_formats = [
{'startDt': f'{year}{month:02d}01', 'endDt': f'{year}{month:02d}{last_day:02d}'},
{'stDt': f'{year}{month:02d}01', 'edDt': f'{year}{month:02d}{last_day:02d}'},
{'fromDate': f'{year}-{month:02d}-01', 'toDate': f'{year}-{month:02d}-{last_day:02d}'},
{'strDt': f'{year}{month:02d}01', 'endDt': f'{year}{month:02d}{last_day:02d}'},
{'ordDt': f'{year}{month:02d}'},
]
endpoints = [
'/ordLedger/listSearch',
'/ordLedger/search',
'/ordLedger/ledgerList',
'/cust/ordLedgerList',
'/cust/ledger',
'/ord/histList',
'/ord/history',
'/ord/list',
]
for endpoint in endpoints:
for params in date_formats:
full_params = {**params, 'custCd': cust_cd}
try:
resp = session.get(f"{API_URL}{endpoint}", params=full_params, timeout=10)
if resp.status_code == 200:
print(f"✓ GET {endpoint} {params}: {resp.status_code}")
try:
data = resp.json()
print(f" -> {str(data)[:300]}")
except:
print(f" -> {resp.text[:200]}")
except Exception as e:
pass
try:
resp = session.post(f"{API_URL}{endpoint}", json=full_params, timeout=10)
if resp.status_code == 200:
print(f"✓ POST {endpoint} {params}: {resp.status_code}")
try:
data = resp.json()
print(f" -> {str(data)[:300]}")
except:
print(f" -> {resp.text[:200]}")
except Exception as e:
pass
print("\n=== 주문 이력 관련 API ===\n")
# 주문 이력 조회 시도
order_endpoints = [
'/ord/ordList',
'/ord/orderHistory',
'/ordReg/list',
'/ordReg/history',
'/order/list',
'/order/history',
]
for endpoint in order_endpoints:
try:
params = {'custCd': cust_cd, 'startDt': f'{year}{month:02d}01', 'endDt': f'{year}{month:02d}{last_day:02d}'}
resp = session.get(f"{API_URL}{endpoint}", params=params, timeout=10)
print(f"GET {endpoint}: {resp.status_code}")
if resp.status_code == 200:
try:
data = resp.json()
print(f" -> {str(data)[:500]}")
except:
print(f" -> {resp.text[:200]}")
except:
pass
print("\n=== custMonth/listSearch 상세 데이터 분석 ===\n")
# 이미 작동하는 API의 데이터 상세 분석
resp = session.get(f"{API_URL}/custMonth/listSearch", params={'custCd': cust_cd, 'year': str(year), 'endDt': f'{year}{month:02d}{last_day:02d}'}, timeout=10)
if resp.status_code == 200:
data = resp.json()
print("월간 데이터 구조:")
for item in data:
print(f"\n월: {item.get('BALANCE_YM')}")
print(f" 매출액(SALE_AMT): {item.get('SALE_AMT'):,}")
print(f" 반품액(BACK_AMT): {item.get('BACK_AMT'):,}")
print(f" 순반품(PURE_BACK_AMT): {item.get('PURE_BACK_AMT'):,}")
print(f" 순매출(TOTAL_AMT): {item.get('TOTAL_AMT'):,}")
print(f" 입금액(PAY_CASH_AMT): {item.get('PAY_CASH_AMT'):,}")
print(f" 전월이월(PRE_TOTAL_AMT): {item.get('PRE_TOTAL_AMT'):,}")
print(f" 월말잔고(BALANCE_A_AMT): {item.get('BALANCE_A_AMT'):,}")
print(f" 회전일수(ROTATE_DAY): {item.get('ROTATE_DAY')}")

View File

@@ -1,84 +0,0 @@
# -*- coding: utf-8 -*-
"""백제약품 get_monthly_sales() 테스트"""
import os
import sys
# wholesale 패키지 경로 추가
sys.path.insert(0, r'c:\Users\청춘약국\source\pharmacy-wholesale-api')
os.chdir(r'c:\Users\청춘약국\source\pharmacy-pos-qr-system\backend')
from dotenv import load_dotenv
load_dotenv()
from wholesale import BaekjeSession
def test_monthly_sales():
print("=" * 60)
print("백제약품 월간 매출 조회 테스트")
print("=" * 60)
session = BaekjeSession()
# 현재 월 조회
from datetime import datetime
now = datetime.now()
year = now.year
month = now.month
print(f"\n1. 현재 월 ({year}-{month:02d}) 조회:")
result = session.get_monthly_sales(year, month)
print(f" Success: {result.get('success')}")
if result.get('success'):
print(f" 월간 매출: {result.get('total_amount'):,}")
print(f" 월간 반품: {result.get('total_returns'):,}")
print(f" 순매출: {result.get('net_amount'):,}")
print(f" 월간 입금: {result.get('total_paid'):,}")
print(f" 월말 잔고: {result.get('ending_balance'):,}")
print(f" 전월이월: {result.get('prev_balance'):,}")
print(f" 회전일수: {result.get('rotate_days')}")
print(f" 조회기간: {result.get('from_date')} ~ {result.get('to_date')}")
else:
print(f" Error: {result.get('error')}")
# 전월 조회
prev_month = month - 1 if month > 1 else 12
prev_year = year if month > 1 else year - 1
print(f"\n2. 전월 ({prev_year}-{prev_month:02d}) 조회:")
result = session.get_monthly_sales(prev_year, prev_month)
print(f" Success: {result.get('success')}")
if result.get('success'):
print(f" 월간 매출: {result.get('total_amount'):,}")
print(f" 월간 반품: {result.get('total_returns'):,}")
print(f" 순매출: {result.get('net_amount'):,}")
print(f" 월간 입금: {result.get('total_paid'):,}")
print(f" 월말 잔고: {result.get('ending_balance'):,}")
print(f" 전월이월: {result.get('prev_balance'):,}")
print(f" 회전일수: {result.get('rotate_days')}")
print(f" 조회기간: {result.get('from_date')} ~ {result.get('to_date')}")
else:
print(f" Error: {result.get('error')}")
# 2달 전 조회
prev_month2 = prev_month - 1 if prev_month > 1 else 12
prev_year2 = prev_year if prev_month > 1 else prev_year - 1
print(f"\n3. 2달 전 ({prev_year2}-{prev_month2:02d}) 조회:")
result = session.get_monthly_sales(prev_year2, prev_month2)
print(f" Success: {result.get('success')}")
if result.get('success'):
print(f" 월간 매출: {result.get('total_amount'):,}")
print(f" 월간 반품: {result.get('total_returns'):,}")
print(f" 순매출: {result.get('net_amount'):,}")
print(f" 월간 입금: {result.get('total_paid'):,}")
print(f" 월말 잔고: {result.get('ending_balance'):,}")
else:
print(f" Error: {result.get('error')}")
print("\n" + "=" * 60)
print("테스트 완료!")
print("=" * 60)
if __name__ == '__main__':
test_monthly_sales()

View File

@@ -1,16 +0,0 @@
# -*- coding: utf-8 -*-
"""Bag.js 분석"""
from sooin_api import SooinSession
import re
session = SooinSession()
session.login()
resp = session.session.get('http://sooinpharm.co.kr/Common/Javascript/Bag.js?v=250228')
js = resp.text
# del 포함된 부분 찾기
lines = js.split('\n')
for i, line in enumerate(lines):
if 'del' in line.lower() and ('kind' in line.lower() or 'bagorder' in line.lower()):
print(f'{i}: {line.strip()[:100]}')

View File

@@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
"""Bag.js 전체에서 del 찾기"""
from sooin_api import SooinSession
import re
session = SooinSession()
session.login()
resp = session.session.get('http://sooinpharm.co.kr/Common/Javascript/Bag.js?v=250228')
js = resp.text
print(f'JS 길이: {len(js)}')
# del 포함된 줄 모두
for i, line in enumerate(js.split('\n')):
line = line.strip()
if 'del' in line.lower():
print(f'{line[:120]}')

View File

@@ -1,16 +0,0 @@
# -*- coding: utf-8 -*-
"""Bag.js 체크박스 관련 찾기"""
from sooin_api import SooinSession
import re
session = SooinSession()
session.login()
resp = session.session.get('http://sooinpharm.co.kr/Common/Javascript/Bag.js?v=250228')
js = resp.text
# chk, checkbox 관련 코드 찾기
lines = js.split('\n')
for i, line in enumerate(lines):
if 'chk' in line.lower() or 'check' in line.lower():
print(f'{i}: {line.strip()[:120]}')

View File

@@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
"""Bag.js AJAX URL 찾기"""
from sooin_api import SooinSession
import re
session = SooinSession()
session.login()
resp = session.session.get('http://sooinpharm.co.kr/Common/Javascript/Bag.js?v=250228')
js = resp.text
# AJAX 호출 찾기 ($.ajax, url:, type: 패턴)
ajax_blocks = re.findall(r'\$\.ajax\s*\(\s*\{[^}]{0,500}\}', js, re.DOTALL)
print(f'AJAX 호출 {len(ajax_blocks)}개 발견:\n')
for i, block in enumerate(ajax_blocks[:5]):
print(f'=== AJAX {i+1} ===')
print(block[:300])
print()

View File

@@ -1,46 +0,0 @@
# -*- coding: utf-8 -*-
"""항목 취소 테스트"""
from sooin_api import SooinSession
import json
session = SooinSession()
session.login()
print('=== 항목 취소 테스트 ===\n')
# 1. 장바구니 비우기
session.clear_cart()
print('1. 장바구니 비움')
# 2. 두 개 담기
session.order_product('073100220', 1, '30T') # 코자정
print('2. 코자정 담음')
session.order_product('652100640', 1) # 스틸녹스
print('3. 스틸녹스 담음')
# 3. 장바구니 확인
cart = session.get_cart()
print(f'\n현재 장바구니:')
print(f' 총 항목: {cart.get("all_items", 0)}')
print(f' 활성(주문포함): {cart.get("total_items", 0)}')
print(f' 취소됨: {cart.get("cancelled_items", 0)}')
for item in cart.get('items', []):
status = '❌ 취소' if item.get('checked') else '✅ 활성'
print(f' [{item.get("row_index")}] {item.get("product_name")} - {status}')
# 4. 첫 번째 항목 취소
print(f'\n4. 첫 번째 항목(idx=0) 취소 시도...')
result = session.cancel_item(row_index=0)
print(f' 결과: {result.get("success")} - {result.get("message", result.get("error", ""))}')
# 5. 다시 확인
cart = session.get_cart()
print(f'\n취소 후 장바구니:')
print(f' 활성: {cart.get("total_items", 0)}')
print(f' 취소됨: {cart.get("cancelled_items", 0)}')
for item in cart.get('items', []):
status = '❌ 취소' if item.get('checked') else '✅ 활성'
print(f' [{item.get("row_index")}] {item.get("product_name")} - {status}')
print('\n=== 완료 ===')

View File

@@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
"""장바구니 추가 테스트 (실제 주문 X)"""
import json
import sys
sys.path.insert(0, r'c:\Users\청춘약국\source\pharmacy-pos-qr-system\backend')
from sooin_api import SooinSession
print("=" * 60)
print("수인약품 API 장바구니 테스트")
print("=" * 60)
session = SooinSession()
# 1. 로그인
print("\n1. 로그인...")
if not session.login():
print("❌ 로그인 실패")
sys.exit(1)
print("✅ 로그인 성공!")
# 2. 장바구니 비우기
print("\n2. 장바구니 비우기...")
result = session.clear_cart()
print(f" 결과: {'성공' if result['success'] else '실패'}")
# 3. 제품 검색
print("\n3. 제품 검색 (KD코드: 073100220 - 코자정)...")
products = session.search_products('073100220', 'kd_code')
print(f" 검색 결과: {len(products)}")
for p in products:
print(f" - {p['product_name']} ({p['specification']}) 재고: {p['stock']} 단가: {p['unit_price']:,}")
print(f" 내부코드: {p['internal_code']}")
# 4. 장바구니 추가
if products:
print("\n4. 장바구니 추가 (첫 번째 제품, 1개)...")
product = products[1] # 30T 선택
result = session.add_to_cart(
internal_code=product['internal_code'],
quantity=1,
stock=product['stock'],
price=product['unit_price']
)
print(f" 결과: {json.dumps(result, ensure_ascii=False, indent=2)}")
# 5. 장바구니 조회
print("\n5. 장바구니 조회...")
cart = session.get_cart()
print(f" 장바구니: {cart['total_items']}개 품목, {cart['total_amount']:,}")
for item in cart['items']:
print(f" - {item['product_name']}: {item['quantity']}개 ({item['amount']:,}원)")
# 6. 장바구니 비우기 (정리)
print("\n6. 장바구니 비우기 (정리)...")
result = session.clear_cart()
print(f" 결과: {'성공' if result['success'] else '실패'}")
print("\n" + "=" * 60)
print("테스트 완료! (실제 주문은 하지 않았습니다)")
print("=" * 60)

View File

@@ -1,114 +0,0 @@
# -*- coding: utf-8 -*-
"""지오영 장바구니 API 직접 테스트 (requests)"""
import requests
from bs4 import BeautifulSoup
import asyncio
from playwright.async_api import async_playwright
async def get_cookies():
"""Playwright로 로그인 후 쿠키 획득"""
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
await page.goto('https://gwn.geoweb.kr/Member/Login')
await page.fill('input[type="text"]', '7390')
await page.fill('input[type="password"]', 'trajet6640')
await page.click('button, input[type="submit"]')
await page.wait_for_load_state('networkidle')
cookies = await page.context.cookies()
await browser.close()
return cookies
def test_cart_api():
# 1. 쿠키 획득
print("1. 로그인 중...")
cookies = asyncio.run(get_cookies())
# 2. requests 세션 설정
session = requests.Session()
for c in cookies:
session.cookies.set(c['name'], c['value'])
session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest'
})
print(f" 쿠키: {[c['name'] for c in cookies]}")
# 3. 제품 검색
print("\n2. 제품 검색...")
search_resp = session.post('https://gwn.geoweb.kr/Home/PartialSearchProduct', data={
'srchText': '643104281',
'srchCate': '',
'prdtType': '',
'prdOrder': '',
'srchCompany': '',
'startdate': '',
'enddate': ''
})
print(f" 검색 응답: {search_resp.status_code}, 길이: {len(search_resp.text)}")
# 4. 장바구니 API 테스트 - 여러 엔드포인트 시도
print("\n3. 장바구니 API 테스트...")
endpoints = [
'/Home/PartialProductCart',
'/Home/AddCart',
'/Order/AddCart',
'/Home/AddToCart',
'/Order/AddToCart',
'/Home/InsertCart',
'/Order/InsertCart',
]
for endpoint in endpoints:
url = f'https://gwn.geoweb.kr{endpoint}'
# 다양한 파라미터 조합 시도
params_list = [
{'prdtCode': '643104281', 'qty': 1},
{'productCode': '643104281', 'quantity': 1},
{'code': '643104281', 'cnt': 1},
{'insCode': '643104281', 'orderQty': 1},
]
for params in params_list:
try:
resp = session.post(url, data=params, timeout=5)
if resp.status_code == 200:
text = resp.text[:200]
if 'error' not in text.lower() and '404' not in text:
print(f"{endpoint}")
print(f" Params: {params}")
print(f" Response: {text[:100]}...")
except Exception as e:
pass
# 5. 현재 장바구니 조회
print("\n4. 장바구니 조회...")
cart_resp = session.post('https://gwn.geoweb.kr/Home/PartialProductCart')
print(f" 응답: {cart_resp.status_code}")
soup = BeautifulSoup(cart_resp.text, 'html.parser')
# 장바구니 테이블에서 상품 찾기
rows = soup.find_all('tr')
print(f" 테이블 행: {len(rows)}")
# HTML에서 장바구니 추가 폼 찾기
forms = soup.find_all('form')
for form in forms:
action = form.get('action', '')
if 'cart' in action.lower() or 'order' in action.lower():
print(f" 폼 발견: {action}")
inputs = form.find_all('input')
for inp in inputs:
print(f" - {inp.get('name')}: {inp.get('value', '')}")
if __name__ == "__main__":
test_cart_api()

View File

@@ -1,44 +0,0 @@
# -*- coding: utf-8 -*-
"""장바구니 디버깅"""
import sys
sys.path.insert(0, r'c:\Users\청춘약국\source\pharmacy-pos-qr-system\backend')
from sooin_api import SooinSession
session = SooinSession()
if not session.login():
print("로그인 실패")
sys.exit(1)
print("로그인 성공!")
# 1. 장바구니 추가 요청의 실제 응답 확인
print("\n=== 장바구니 추가 요청 ===")
data = {
'qty_0': '1',
'pc_0': '32495',
'stock_0': '238',
'saleqty_0': '0',
'price_0': '14220',
'soldout_0': 'N',
'ordunitqty_0': '1',
'bidqty_0': '0',
'outqty_0': '0',
'overqty_0': '0',
'manage_0': 'N',
'prodno_0': '',
'termdt_0': ''
}
resp = session.session.post(session.BAG_URL, data=data, timeout=15)
print(f"Status: {resp.status_code}")
print(f"URL: {resp.url}")
print(f"\n응답 (처음 2000자):\n{resp.text[:2000]}")
# 2. 장바구니 조회 응답 확인
print("\n\n=== 장바구니 조회 요청 ===")
params = {'currVenCd': session.VENDOR_CODE}
resp2 = session.session.get(session.BAG_URL, params=params, timeout=15)
print(f"Status: {resp2.status_code}")
print(f"URL: {resp2.url}")
print(f"\n응답 (처음 3000자):\n{resp2.text[:3000]}")

View File

@@ -1,127 +0,0 @@
# -*- coding: utf-8 -*-
"""장바구니 조회 API 테스트"""
import requests
from bs4 import BeautifulSoup
import asyncio
from playwright.async_api import async_playwright
import json
async def get_cookies():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
await page.goto('https://gwn.geoweb.kr/Member/Login')
await page.fill('input[type="text"]', '7390')
await page.fill('input[type="password"]', 'trajet6640')
await page.click('button, input[type="submit"]')
await page.wait_for_load_state('networkidle')
cookies = await page.context.cookies()
await browser.close()
return cookies
def test():
print("="*60)
print("장바구니 조회 API 테스트")
print("="*60)
cookies = asyncio.run(get_cookies())
session = requests.Session()
for c in cookies:
session.cookies.set(c['name'], c['value'])
session.headers.update({
'User-Agent': 'Mozilla/5.0',
'X-Requested-With': 'XMLHttpRequest'
})
# 1. 먼저 제품 하나 담기
print("\n1. 테스트용 제품 담기...")
search_resp = session.post('https://gwn.geoweb.kr/Home/PartialSearchProduct',
data={'srchText': '661700390'})
soup = BeautifulSoup(search_resp.text, 'html.parser')
product_div = soup.find('div', class_='div-product-detail')
if product_div:
lis = product_div.find_all('li')
internal_code = lis[0].get_text(strip=True)
cart_resp = session.post('https://gwn.geoweb.kr/Home/DataCart/add', data={
'productCode': internal_code,
'moveCode': '',
'orderQty': 3
})
print(f" 담기 결과: {cart_resp.json()}")
# 2. 장바구니 조회
print("\n2. 장바구니 조회 (PartialProductCart)...")
cart_resp = session.post('https://gwn.geoweb.kr/Home/PartialProductCart')
print(f" 상태: {cart_resp.status_code}")
print(f" 길이: {len(cart_resp.text)}")
# HTML 파싱
soup = BeautifulSoup(cart_resp.text, 'html.parser')
# 테이블 찾기
tables = soup.find_all('table')
print(f" 테이블 수: {len(tables)}")
# 장바구니 항목 파싱
cart_items = []
# div_cart_detail 클래스 찾기
cart_divs = soup.find_all('div', class_='div_cart_detail')
print(f" cart_detail divs: {len(cart_divs)}")
for div in cart_divs:
lis = div.find_all('li')
if len(lis) >= 5:
item = {
'product_code': lis[0].get_text(strip=True) if len(lis) > 0 else '',
'move_code': lis[1].get_text(strip=True) if len(lis) > 1 else '',
'quantity': lis[2].get_text(strip=True) if len(lis) > 2 else '',
'price': lis[3].get_text(strip=True) if len(lis) > 3 else '',
'total': lis[4].get_text(strip=True) if len(lis) > 4 else '',
}
cart_items.append(item)
print(f" - {item}")
# 테이블 행 분석
print("\n 테이블 행 분석:")
for table in tables:
rows = table.find_all('tr')
for row in rows[:5]:
cells = row.find_all(['td', 'th'])
if cells:
texts = [c.get_text(strip=True)[:20] for c in cells[:6]]
print(f" {texts}")
# 3. 다른 API 시도
print("\n3. 다른 장바구니 API 시도...")
endpoints = [
'/Home/GetCartList',
'/Home/CartList',
'/Order/GetCart',
'/Order/CartList',
'/Home/DataCart/list',
]
for ep in endpoints:
try:
resp = session.post(f'https://gwn.geoweb.kr{ep}', timeout=5)
if resp.status_code == 200 and len(resp.text) > 100:
print(f"{ep}: {len(resp.text)} bytes")
print(f" {resp.text[:100]}...")
except:
pass
# 4. 장바구니 비우기
print("\n4. 장바구니 비우기...")
session.post('https://gwn.geoweb.kr/Home/DataCart/delAll')
print(" 완료")
if __name__ == "__main__":
test()

52
backend/test_checkbox.py Normal file
View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import SooinSession
from bs4 import BeautifulSoup
import re
s = SooinSession()
s.login()
s.clear_cart()
result = s.search_products('코자정')
product = result['items'][0]
s.add_to_cart(product['internal_code'], qty=1, price=product['price'], stock=product['stock'])
resp = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
soup = BeautifulSoup(resp.content, 'html.parser')
form = soup.find('form', {'id': 'frmBag'})
form_data = {}
for inp in form.find_all('input'):
name = inp.get('name', '')
if not name: continue
inp_type = inp.get('type', '').lower()
if inp_type == 'checkbox':
# 체크박스는 'on' 값으로 전송!
form_data[name] = 'on'
else:
form_data[name] = inp.get('value', '')
form_data['kind'] = 'order'
form_data['x'] = '10'
form_data['y'] = '10'
print('체크박스 포함된 form_data:')
print(f" chk_0: {form_data.get('chk_0')}")
resp = s.session.post(s.BAG_URL, data=form_data, timeout=30)
alert_match = re.search(r'alert\("([^"]*)"\)', resp.text)
alert_msg = alert_match.group(1) if alert_match else 'N/A'
print(f'alert 메시지: {alert_msg}')
# 장바구니 확인
resp2 = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
soup2 = BeautifulSoup(resp2.content, 'html.parser')
int_array = soup2.find('input', {'name': 'intArray'})
val = int_array.get('value') if int_array else '없음'
print(f'주문 후 intArray: {val}')
if val == '-1':
print('\n🎉 주문 성공!')
else:
print('\n❌ 주문 실패')

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
"""체크박스 HTML 상태 확인"""
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import SooinSession
from bs4 import BeautifulSoup
import re
s = SooinSession()
s.login()
s.clear_cart()
# 품목 담기
r1 = s.search_products('코자정')
p1 = r1['items'][0]
s.add_to_cart(p1['internal_code'], qty=1, price=p1['price'], stock=p1['stock'])
# 취소하기 전 HTML
print('=== 취소 전 HTML ===')
resp = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
soup = BeautifulSoup(resp.content, 'html.parser')
for cb in soup.find_all('input', {'type': 'checkbox'}):
name = cb.get('name', '')
checked = cb.get('checked')
print(f"체크박스 {name}: checked={checked}")
# 취소
print('\n=== 취소 실행 ===')
s.cancel_item(row_index=0)
# 취소 후 HTML
print('\n=== 취소 후 HTML ===')
resp2 = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
soup2 = BeautifulSoup(resp2.content, 'html.parser')
for cb in soup2.find_all('input', {'type': 'checkbox'}):
name = cb.get('name', '')
checked = cb.get('checked')
print(f"체크박스 {name}: checked={checked}")
# 체크박스 HTML 전체 출력
cb = soup2.find('input', {'type': 'checkbox'})
if cb:
print(f"\n전체 HTML: {cb}")

View File

@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
"""체크박스 로직 테스트 - 체크 안 함 vs 체크함"""
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import SooinSession
from bs4 import BeautifulSoup
import re
s = SooinSession()
s.login()
s.clear_cart()
# 장바구니에 품목 추가
result = s.search_products('코자정')
product = result['items'][0]
s.add_to_cart(product['internal_code'], qty=1, price=product['price'], stock=product['stock'])
print("="*60)
print("테스트 1: 체크박스 제외 (체크 안 함 = 주문 포함)")
print("="*60)
resp = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
soup = BeautifulSoup(resp.content, 'html.parser')
form = soup.find('form', {'id': 'frmBag'})
form_data = {}
for inp in form.find_all('input'):
name = inp.get('name', '')
if not name: continue
inp_type = inp.get('type', '').lower()
if inp_type == 'checkbox':
continue # 체크박스 제외!
form_data[name] = inp.get('value', '')
print(f"chk_0 전송됨? {'chk_0' in form_data}")
print(f"intArray: {form_data.get('intArray')}")
resp = s.session.post(
s.ORDER_END_URL,
data=form_data,
headers={'Content-Type': 'application/x-www-form-urlencoded'},
timeout=30
)
alert_match = re.search(r'alert\("([^"]*)"\)', resp.text)
alert_msg = alert_match.group(1) if alert_match else ''
print(f"응답 alert: '{alert_msg}'")
# 장바구니 확인
resp2 = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
soup2 = BeautifulSoup(resp2.content, 'html.parser')
int_array = soup2.find('input', {'name': 'intArray'})
val = int_array.get('value') if int_array else 'N/A'
print(f"주문 후 intArray: {val}")
if '주문이 완료' in alert_msg:
print("✅ 성공!")
else:
print("❌ 실패")

View File

@@ -1,74 +0,0 @@
# -*- coding: utf-8 -*-
"""지오영 DataCart API 테스트"""
import requests
import asyncio
from playwright.async_api import async_playwright
import time
async def get_cookies():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
await page.goto('https://gwn.geoweb.kr/Member/Login')
await page.fill('input[type="text"]', '7390')
await page.fill('input[type="password"]', 'trajet6640')
await page.click('button, input[type="submit"]')
await page.wait_for_load_state('networkidle')
cookies = await page.context.cookies()
await browser.close()
return cookies
def test_datacart():
print("1. 로그인 중...")
start = time.time()
cookies = asyncio.run(get_cookies())
print(f" 로그인 완료: {time.time()-start:.1f}")
session = requests.Session()
for c in cookies:
session.cookies.set(c['name'], c['value'])
session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
})
# 2. 장바구니 추가 테스트
print("\n2. 장바구니 추가 테스트...")
start = time.time()
resp = session.post('https://gwn.geoweb.kr/Home/DataCart/add', data={
'productCode': '643104281', # 하일렌플러스
'moveCode': '',
'orderQty': 1
})
print(f" 소요시간: {time.time()-start:.1f}")
print(f" 상태코드: {resp.status_code}")
print(f" 응답: {resp.text[:500]}")
# JSON 파싱
try:
result = resp.json()
print(f" result: {result.get('result')}")
print(f" msg: {result.get('msg')}")
except:
pass
# 3. 장바구니 조회
print("\n3. 장바구니 조회...")
cart_resp = session.post('https://gwn.geoweb.kr/Home/PartialProductCart')
print(f" 응답 길이: {len(cart_resp.text)}")
# 장바구니에 상품 있는지 확인
if '643104281' in cart_resp.text or '하일렌' in cart_resp.text:
print(" ✓ 장바구니에 상품 추가됨!")
else:
print(" ? 장바구니 확인 필요")
if __name__ == "__main__":
test_datacart()

View File

@@ -1,83 +0,0 @@
# -*- coding: utf-8 -*-
"""지오영 검색 → 장바구니 추가 테스트"""
import requests
from bs4 import BeautifulSoup
import asyncio
from playwright.async_api import async_playwright
import time
import re
async def get_cookies():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
await page.goto('https://gwn.geoweb.kr/Member/Login')
await page.fill('input[type="text"]', '7390')
await page.fill('input[type="password"]', 'trajet6640')
await page.click('button, input[type="submit"]')
await page.wait_for_load_state('networkidle')
cookies = await page.context.cookies()
await browser.close()
return cookies
def test():
print("1. 로그인...")
cookies = asyncio.run(get_cookies())
session = requests.Session()
for c in cookies:
session.cookies.set(c['name'], c['value'])
session.headers.update({
'User-Agent': 'Mozilla/5.0',
'X-Requested-With': 'XMLHttpRequest'
})
# 2. 검색
print("\n2. 제품 검색 (661700390 - 콩코르정)...")
search_resp = session.post('https://gwn.geoweb.kr/Home/PartialSearchProduct', data={
'srchText': '661700390'
})
soup = BeautifulSoup(search_resp.text, 'html.parser')
# 제품 코드 찾기 - data 속성이나 hidden input에서
rows = soup.find_all('tr')
print(f" 테이블 행: {len(rows)}")
# HTML 구조 분석
for row in rows[:2]:
tds = row.find_all('td')
if tds:
print(f" TD 개수: {len(tds)}")
for i, td in enumerate(tds[:8]):
text = td.get_text(strip=True)[:30]
onclick = td.get('onclick', '')[:50]
data_attrs = {k:v for k,v in td.attrs.items() if k.startswith('data')}
print(f" [{i}] {text} | onclick={onclick} | data={data_attrs}")
# onclick에서 제품 코드 추출
onclick_pattern = re.findall(r"onclick=['\"]([^'\"]+)['\"]", search_resp.text)
for oc in onclick_pattern[:3]:
print(f" onclick: {oc[:100]}")
# SelectProduct 함수 호출에서 인덱스 확인
select_pattern = re.findall(r'SelectProduct\s*\(\s*(\d+)', search_resp.text)
print(f" SelectProduct 인덱스: {select_pattern[:3]}")
# div-product-detail에서 제품 코드 찾기
product_divs = soup.find_all('div', class_='div-product-detail')
print(f" product-detail divs: {len(product_divs)}")
for div in product_divs[:2]:
lis = div.find_all('li')
if lis:
print(f" li 개수: {len(lis)}")
for i, li in enumerate(lis[:5]):
print(f" [{i}] {li.get_text(strip=True)[:50]}")
if __name__ == "__main__":
test()

View File

@@ -1,105 +0,0 @@
# -*- coding: utf-8 -*-
"""지오영 장바구니 추가 - 정확한 productCode로 테스트"""
import requests
from bs4 import BeautifulSoup
import asyncio
from playwright.async_api import async_playwright
import time
async def get_cookies():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
await page.goto('https://gwn.geoweb.kr/Member/Login')
await page.fill('input[type="text"]', '7390')
await page.fill('input[type="password"]', 'trajet6640')
await page.click('button, input[type="submit"]')
await page.wait_for_load_state('networkidle')
cookies = await page.context.cookies()
await browser.close()
return cookies
def test():
print("="*60)
print("지오영 API 직접 호출 테스트")
print("="*60)
# 1. 로그인
print("\n1. 로그인...")
start = time.time()
cookies = asyncio.run(get_cookies())
print(f" 완료: {time.time()-start:.1f}")
session = requests.Session()
for c in cookies:
session.cookies.set(c['name'], c['value'])
session.headers.update({
'User-Agent': 'Mozilla/5.0',
'X-Requested-With': 'XMLHttpRequest'
})
# 2. 검색해서 productCode 획득
print("\n2. 제품 검색...")
start = time.time()
search_resp = session.post('https://gwn.geoweb.kr/Home/PartialSearchProduct', data={
'srchText': '661700390' # 콩코르정
})
print(f" 완료: {time.time()-start:.1f}")
soup = BeautifulSoup(search_resp.text, 'html.parser')
product_div = soup.find('div', class_='div-product-detail')
if product_div:
lis = product_div.find_all('li')
product_code = lis[0].get_text(strip=True) if lis else None
print(f" productCode: {product_code}")
else:
print(" 제품 없음!")
return
# 3. 장바구니 추가
print("\n3. 장바구니 추가...")
start = time.time()
cart_resp = session.post('https://gwn.geoweb.kr/Home/DataCart/add', data={
'productCode': product_code,
'moveCode': '',
'orderQty': 2
})
print(f" 완료: {time.time()-start:.1f}")
print(f" 상태: {cart_resp.status_code}")
try:
result = cart_resp.json()
print(f" result: {result.get('result')}")
print(f" msg: {result.get('msg', 'OK')}")
if result.get('result') == 1:
print("\n ✅ 장바구니 추가 성공!")
else:
print(f"\n ❌ 실패: {result.get('msg')}")
except:
print(f" 응답: {cart_resp.text[:200]}")
# 4. 장바구니 확인
print("\n4. 장바구니 확인...")
cart_check = session.post('https://gwn.geoweb.kr/Home/PartialProductCart')
if '콩코르' in cart_check.text or product_code in cart_check.text:
print(" ✅ 장바구니에 상품 있음!")
else:
print(" 확인 필요")
# 전체 시간
print("\n" + "="*60)
print("총 API 호출 시간: 검색 + 장바구니 추가 = ~3초")
print("(Playwright 30초+ 대비 10배 이상 빠름!)")
print("="*60)
if __name__ == "__main__":
test()

View File

@@ -1,101 +0,0 @@
# -*- coding: utf-8 -*-
"""지오영 주문 확정 API 테스트"""
import requests
from bs4 import BeautifulSoup
import asyncio
from playwright.async_api import async_playwright
import time
async def get_cookies():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
await page.goto('https://gwn.geoweb.kr/Member/Login')
await page.fill('input[type="text"]', '7390')
await page.fill('input[type="password"]', 'trajet6640')
await page.click('button, input[type="submit"]')
await page.wait_for_load_state('networkidle')
cookies = await page.context.cookies()
await browser.close()
return cookies
def test():
print("="*60)
print("지오영 전체 주문 플로우 테스트")
print("="*60)
# 1. 로그인
print("\n1. 로그인...")
start = time.time()
cookies = asyncio.run(get_cookies())
print(f" 완료: {time.time()-start:.1f}")
session = requests.Session()
for c in cookies:
session.cookies.set(c['name'], c['value'])
session.headers.update({
'User-Agent': 'Mozilla/5.0',
'X-Requested-With': 'XMLHttpRequest'
})
# 2. 검색 → productCode 획득
print("\n2. 제품 검색...")
start = time.time()
search_resp = session.post('https://gwn.geoweb.kr/Home/PartialSearchProduct', data={
'srchText': '661700390'
})
soup = BeautifulSoup(search_resp.text, 'html.parser')
product_div = soup.find('div', class_='div-product-detail')
lis = product_div.find_all('li') if product_div else []
product_code = lis[0].get_text(strip=True) if lis else None
print(f" productCode: {product_code}")
print(f" 완료: {time.time()-start:.1f}")
# 3. 장바구니 추가
print("\n3. 장바구니 추가...")
start = time.time()
cart_resp = session.post('https://gwn.geoweb.kr/Home/DataCart/add', data={
'productCode': product_code,
'moveCode': '',
'orderQty': 1
})
result = cart_resp.json()
print(f" result: {result.get('result')}")
print(f" 완료: {time.time()-start:.1f}")
if result.get('result') != 1:
print(f" ❌ 장바구니 추가 실패: {result.get('msg')}")
return
# 4. 주문 확정 (실제 주문!) - 테스트이므로 실행 안함
print("\n4. 주문 확정 API 테스트...")
print(" ⚠️ 실제 주문이 들어가므로 테스트 중지!")
print(" API: POST /Home/DataOrder")
print(" params: { p_desc: '메모' }")
# 실제 주문 코드 (주석 처리)
# order_resp = session.post('https://gwn.geoweb.kr/Home/DataOrder', data={
# 'p_desc': '테스트 주문'
# })
# print(f" 응답: {order_resp.text[:200]}")
# 5. 장바구니 비우기 (테스트용)
print("\n5. 장바구니 비우기...")
# 장바구니에서 삭제
clear_resp = session.post('https://gwn.geoweb.kr/Home/DataCart/delAll')
print(f" 상태: {clear_resp.status_code}")
print("\n" + "="*60)
print("✅ 전체 API 플로우 확인 완료!")
print("")
print("1. 검색: POST /Home/PartialSearchProduct")
print("2. 장바구니: POST /Home/DataCart/add")
print("3. 주문확정: POST /Home/DataOrder")
print("="*60)
if __name__ == "__main__":
test()

View File

@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
"""submit_order 디버깅"""
import sys; sys.path.insert(0, '.'); import wholesale_path
from bs4 import BeautifulSoup
import re
import importlib
import wholesale.sooin
importlib.reload(wholesale.sooin)
from wholesale import SooinSession
SooinSession._instance = None
s = SooinSession()
s.login()
s.clear_cart()
# 품목 담기
r1 = s.search_products('코자정')
s.add_to_cart(r1['items'][0]['internal_code'], qty=1, price=r1['items'][0]['price'], stock=r1['items'][0]['stock'])
# 취소
s.cancel_item(row_index=0)
# Bag.asp GET
print('=== Bag.asp GET 후 form 분석 ===')
resp = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
soup = BeautifulSoup(resp.content, 'html.parser')
form = soup.find('form', {'id': 'frmBag'})
form_data = {}
for inp in form.find_all('input'):
name = inp.get('name', '')
if not name:
continue
inp_type = inp.get('type', 'text').lower()
if inp_type == 'checkbox':
checked = inp.get('checked')
print(f"체크박스 {name}: checked={checked}, type={type(checked)}")
if checked is not None:
form_data[name] = 'on'
print(f" → form_data['{name}'] = 'on' (취소됨, 제외)")
else:
print(f" → 안 보냄 (활성, 포함)")
continue
form_data[name] = inp.get('value', '')
print(f"\n체크박스 관련 form_data: {[(k,v) for k,v in form_data.items() if 'chk' in k]}")

View File

@@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
from sooin_api import SooinSession
import re
session = SooinSession()
session.login()
resp = session.session.get('http://sooinpharm.co.kr/Service/Order/Bag.asp?currVenCd=50911')
# 개별 삭제 관련 찾기
html = resp.text
# kind 파라미터 종류
kinds = re.findall(r'kind=(\w+)', html)
print('kind 파라미터들:', list(set(kinds)))
# 체크박스 관련 함수
if 'chk_' in html:
print('\n체크박스 있음 (chk_0, chk_1 등)')
# delOne 같은 개별 삭제
if 'delOne' in html or 'deleteOne' in html:
print('개별 삭제 함수 있음')
# 선택삭제 버튼
if '선택삭제' in html or '선택 삭제' in html:
print('선택삭제 버튼 있음')
# 전체 삭제 URL
del_url = re.search(r'BagOrder\.asp\?kind=del[^"\'>\s]*', html)
if del_url:
print(f'\n전체 삭제 URL: {del_url.group()}')

View File

@@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
from sooin_api import SooinSession
import re
session = SooinSession()
session.login()
resp = session.session.get('http://sooinpharm.co.kr/Service/Order/Bag.asp?currVenCd=50911')
html = resp.text
# 모든 script 내용 출력
scripts = re.findall(r'<script[^>]*>(.*?)</script>', html, re.DOTALL)
for i, script in enumerate(scripts):
# 삭제/취소 관련 있으면 출력
if any(x in script.lower() for x in ['del', 'cancel', 'remove', 'chk_']):
print(f'=== Script {i+1} ===')
# 함수 시그니처만 추출
funcs = re.findall(r'function\s+\w+[^{]+', script)
for f in funcs[:5]:
print(f' {f.strip()}')
# 특정 패턴 찾기
patterns = re.findall(r'(delPhysic|cancelOrder|chkBag|selectDel)[^(]*\([^)]*\)', script)
if patterns:
print(f' Patterns: {patterns[:5]}')

View File

@@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
from sooin_api import SooinSession
import re
session = SooinSession()
session.login()
resp = session.session.get('http://sooinpharm.co.kr/Service/Order/Bag.asp?currVenCd=50911')
html = resp.text
# 모든 <a> 태그의 href와 onclick 찾기
links = re.findall(r'<a[^>]*(href|onclick)=["\']([^"\']+)["\'][^>]*>', html)
for attr, val in links:
if 'del' in val.lower() or 'cancel' in val.lower():
print(f'{attr}: {val[:100]}')
print('\n--- form actions ---')
forms = re.findall(r'<form[^>]*action=["\']([^"\']+)["\']', html)
for f in forms:
print(f'form action: {f}')
print('\n--- hidden inputs ---')
hiddens = re.findall(r'<input[^>]*type=["\']hidden["\'][^>]*name=["\']([^"\']+)["\'][^>]*value=["\']([^"\']*)["\']', html)
for name, val in hiddens[:10]:
print(f'{name}: {val}')

View File

@@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
"""체크박스로 삭제 테스트"""
from sooin_api import SooinSession
import re
session = SooinSession()
session.login()
# Bag.asp의 JavaScript 전체 확인
resp = session.session.get('http://sooinpharm.co.kr/Service/Order/Bag.asp?currVenCd=50911')
# onclick 이벤트들 찾기
onclicks = re.findall(r'onclick="([^"]*)"', resp.text)
print('onclick handlers:')
for oc in onclicks[:10]:
if len(oc) < 200:
print(f' {oc}')
# form의 name과 action
forms = re.findall(r'<form[^>]*name="([^"]*)"[^>]*action="([^"]*)"', resp.text)
print('\nForms:')
for name, action in forms:
print(f' {name}: {action}')
# 삭제 관련 JavaScript 함수 찾기
scripts = re.findall(r'function\s+(\w+Del\w*|\w+Cancel\w*|\w+Remove\w*)\s*\([^)]*\)\s*\{[^}]{0,300}', resp.text, re.IGNORECASE)
print('\nDelete functions:')
for s in scripts[:5]:
print(f' {s[:100]}...')

View File

@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
"""HTML 전체 분석"""
from sooin_api import SooinSession
session = SooinSession()
session.login()
resp = session.session.get('http://sooinpharm.co.kr/Service/Order/Bag.asp?currVenCd=50911')
# 전체 저장해서 분석
with open('bag_page.html', 'w', encoding='utf-8') as f:
f.write(resp.text)
print('bag_page.html 저장됨')
print(f'길이: {len(resp.text)}')
# 현재 장바구니 상태
cart = session.get_cart()
print(f'장바구니: {cart.get("total_items", 0)}')
for item in cart.get('items', []):
print(f' - {item.get("product_name")}')

View File

@@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
"""개별 삭제 테스트"""
from sooin_api import SooinSession
session = SooinSession()
session.login()
# 1. 장바구니 비우기
session.clear_cart()
print('1. 장바구니 비움')
# 2. 두 개 담기
session.order_product('073100220', 1, '30T') # 코자정
print('2. 코자정 담음')
session.order_product('652100640', 1) # 스틸녹스
print('3. 스틸녹스 담음')
# 장바구니 확인
cart = session.get_cart()
count = cart.get('total_items', 0)
print(f' 현재 장바구니: {count}')
for item in cart.get('items', []):
print(f' - {item.get("product_name", "")}')
# 3. 첫 번째 항목만 삭제 (idx=0)
print('\n4. idx=0 (첫 번째) 삭제...')
resp = session.session.get(
'http://sooinpharm.co.kr/Service/Order/BagOrder.asp',
params={'kind': 'delOne', 'idx': '0', 'currVenCd': '50911'}
)
# 장바구니 다시 확인
cart = session.get_cart()
count = cart.get('total_items', 0)
print(f' 삭제 후: {count}')
for item in cart.get('items', []):
print(f' - {item.get("product_name", "")}')

View File

@@ -1,33 +0,0 @@
# -*- coding: utf-8 -*-
"""pc 파라미터로 삭제 테스트"""
from sooin_api import SooinSession
session = SooinSession()
session.login()
# 장바구니 확인
resp = session.session.get('http://sooinpharm.co.kr/Service/Order/Bag.asp?currVenCd=50911')
# hidden input들 확인
import re
hiddens = re.findall(r'name="(pc_\d+|idx_\d+|bagIdx_\d+)"[^>]*value="([^"]*)"', resp.text)
print('Hidden fields:')
for name, val in hiddens[:10]:
print(f' {name}: {val}')
# 장바구니 iframe의 실제 삭제 로직 찾기
# del + pc 조합 시도
print('\ndel with pc 시도...')
resp = session.session.get(
'http://sooinpharm.co.kr/Service/Order/BagOrder.asp',
params={
'kind': 'delOne',
'idx': '0',
'pc': '31840', # 스틸녹스 코드
'currVenCd': '50911'
}
)
# 결과
cart = session.get_cart()
print(f'삭제 후: {cart.get("total_items", 0)}')

View File

@@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
"""개별 삭제 POST 테스트"""
from sooin_api import SooinSession
session = SooinSession()
session.login()
# 장바구니 확인
cart = session.get_cart()
print(f'현재: {cart.get("total_items", 0)}')
# POST로 삭제 시도
print('\nPOST로 delOne 시도...')
resp = session.session.post(
'http://sooinpharm.co.kr/Service/Order/BagOrder.asp',
data={
'kind': 'delOne',
'idx': '0',
'currVenCd': '50911'
}
)
print(f'응답: {resp.text[:300]}')
# 다시 확인
cart = session.get_cart()
print(f'\n삭제 후: {cart.get("total_items", 0)}')

View File

@@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
import sys
import re
sys.path.insert(0, r'c:\Users\청춘약국\source\pharmacy-pos-qr-system\backend')
from sooin_api import SooinSession
session = SooinSession()
if session.login():
# 직접 요청해서 인코딩 확인
params = {
'so': '0', 'so2': '0', 'so3': '2',
'tx_physic': '073100220',
'tx_ven': '50911',
'currVenNm': '청춘약국'
}
resp = session.session.get(session.ORDER_URL, params=params, timeout=15)
print('Content-Type:', resp.headers.get('Content-Type'))
print('Encoding:', resp.encoding)
print('Apparent Encoding:', resp.apparent_encoding)
# charset 확인
charset_match = re.search(r'charset=([^\s;"]+)', resp.text[:1000])
print('HTML charset:', charset_match.group(1) if charset_match else 'Not found')
# 직접 디코딩 테스트
print('\n--- 디코딩 테스트 ---')
test_encodings = ['euc-kr', 'cp949', 'utf-8', 'iso-8859-1']
for enc in test_encodings:
try:
decoded = resp.content.decode(enc, errors='replace')
# 코자정이 포함되어 있는지 확인
if '코자정' in decoded:
print(f'{enc}: 성공! (코자정 발견)')
elif '' in decoded or '' in decoded:
print(f'{enc}: 부분 실패 (깨진 문자 발견)')
else:
print(f'{enc}: 확인 불가')
except Exception as e:
print(f'{enc}: 오류 - {e}')

View File

@@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
"""Flask Blueprint 테스트"""
import wholesale_path
from geoyoung_api import geoyoung_bp, get_geo_session
from sooin_api import sooin_bp, get_sooin_session
print('=== Flask Blueprint 테스트 ===\n')
# Blueprint 확인
print(f'지오영 Blueprint: {geoyoung_bp.name} ({geoyoung_bp.url_prefix})')
print(f'수인약품 Blueprint: {sooin_bp.name} ({sooin_bp.url_prefix})')
# 세션 함수 확인
geo_session = get_geo_session()
sooin_session = get_sooin_session()
print(f'\n지오영 세션: {geo_session}')
print(f'수인약품 세션: {sooin_session}')
# 라우트 확인
print('\n지오영 라우트:')
for rule in geoyoung_bp.deferred_functions:
print(f' - {rule}')
print('\n✅ Blueprint 로드 성공!')

View File

@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
"""지오영 full_order vs quick_order 비교 테스트"""
import sys; sys.path.insert(0, '.'); import wholesale_path
import importlib
import wholesale.geoyoung
importlib.reload(wholesale.geoyoung)
from wholesale import GeoYoungSession
GeoYoungSession._instance = None
g = GeoYoungSession()
g.login()
g.clear_cart()
print('='*60)
print('테스트 1: quick_order 직접 호출')
print('='*60)
result1 = g.quick_order(
kd_code='라식스',
quantity=1,
spec=None,
check_stock=True
)
print(f"결과: {result1}")
print('\n' + '='*60)
print('테스트 2: full_order 호출 (auto_confirm=False)')
print('='*60)
g.clear_cart()
result2 = g.full_order(
kd_code='코자정',
quantity=1,
specification=None,
check_stock=True,
auto_confirm=False # 장바구니만
)
print(f"결과: {result2}")
print('\n' + '='*60)
print('장바구니 확인')
print('='*60)
cart = g.get_cart()
print(f"장바구니: {cart['total_items']}")
for item in cart['items']:
print(f" - {item['product_name']}")

View File

@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
"""
지오영 장바구니 키 매칭 디버그 테스트
- 장바구니 조회 시 반환되는 키와 add_to_cart 시 사용하는 키가 일치하는지 확인
"""
import sys
sys.path.insert(0, r'c:\Users\청춘약국\source\pharmacy-wholesale-api')
from wholesale.geoyoung import GeoYoungSession
def test_cart_keys():
"""장바구니 항목의 키 확인"""
session = GeoYoungSession()
print("=" * 60)
print("지오영 장바구니 키 매칭 디버그")
print("=" * 60)
# 로그인
if not session.login():
print("❌ 로그인 실패")
return
print("✅ 로그인 성공")
# 현재 장바구니 조회
cart = session.get_cart()
print(f"\n📦 장바구니 조회 결과:")
print(f" - success: {cart.get('success')}")
print(f" - total_items: {cart.get('total_items')}")
print(f" - total_amount: {cart.get('total_amount'):,}")
if not cart.get('items'):
print("\n⚠️ 장바구니가 비어있습니다!")
return
print(f"\n📋 장바구니 항목 상세:")
for i, item in enumerate(cart.get('items', [])):
print(f"\n [{i}] {item.get('product_name')}")
print(f" - row_index: {item.get('row_index')}")
print(f" - product_code: {item.get('product_code')}")
print(f" - internal_code: {item.get('internal_code')}")
print(f" - center: {item.get('center')}")
print(f" - quantity: {item.get('quantity')}")
print(f" - unit_price: {item.get('unit_price'):,}")
print(f" - amount: {item.get('amount'):,}")
print(f" - active: {item.get('active')}")
# 키 확인
code = item.get('product_code') or item.get('internal_code')
print(f" → 사용될 키: {code}")
print("\n" + "=" * 60)
print("💡 submit_order_selective()에서 사용하는 키가 위 'product_code'와 일치해야 합니다!")
print("=" * 60)
if __name__ == "__main__":
test_cart_keys()

View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
"""지오영 장바구니 아이템 키 확인"""
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import GeoYoungSession
import json
g = GeoYoungSession()
g.login()
cart = g.get_cart()
print(f"장바구니: {cart['total_items']}\n")
if cart['items']:
print("첫 번째 아이템 키:")
item = cart['items'][0]
print(json.dumps(item, indent=2, ensure_ascii=False, default=str))

60
backend/test_geo_clear.py Normal file
View File

@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
"""
지오영 clear_cart API 테스트
"""
import sys
sys.path.insert(0, r'c:\Users\청춘약국\source\pharmacy-wholesale-api')
from dotenv import load_dotenv
load_dotenv(r'c:\Users\청춘약국\source\pharmacy-wholesale-api\.env')
from wholesale.geoyoung import GeoYoungSession
def test_clear_cart():
session = GeoYoungSession()
print("=" * 60)
print("지오영 clear_cart API 테스트")
print("=" * 60)
# 1. 로그인
if not session.login():
print("❌ 로그인 실패")
return
print("✅ 로그인 성공\n")
# 2. 현재 장바구니 조회
print("📦 [BEFORE] 장바구니 조회:")
cart = session.get_cart()
print(f" - 성공: {cart.get('success')}")
print(f" - 품목 수: {cart.get('total_items')}")
for item in cart.get('items', []):
print(f"{item.get('product_name')} (code: {item.get('product_code')}, qty: {item.get('quantity')})")
if not cart.get('items'):
print("\n⚠️ 장바구니가 이미 비어있어요! 테스트를 위해 뭔가 담아주세요.")
return
# 3. clear_cart 호출
print("\n🗑️ clear_cart() 호출...")
clear_result = session.clear_cart()
print(f" - 결과: {clear_result}")
# 4. 다시 장바구니 조회
import time
time.sleep(1) # 서버 처리 대기
print("\n📦 [AFTER] 장바구니 조회:")
cart_after = session.get_cart()
print(f" - 성공: {cart_after.get('success')}")
print(f" - 품목 수: {cart_after.get('total_items')}")
for item in cart_after.get('items', []):
print(f"{item.get('product_name')} (code: {item.get('product_code')})")
if not cart_after.get('items'):
print("\n✅ clear_cart 성공! 장바구니가 비워졌습니다.")
else:
print(f"\n❌ clear_cart 실패! 아직 {len(cart_after.get('items', []))}개 품목 남아있음")
if __name__ == "__main__":
test_clear_cart()

View File

@@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
"""
지오영 "전체삭제" 버튼 분석 - Playwright로 실제 버튼 클릭 시 API 확인
"""
import asyncio
import os
from dotenv import load_dotenv
load_dotenv(r'c:\Users\청춘약국\source\pharmacy-wholesale-api\.env')
async def analyze_delete_all():
from playwright.async_api import async_playwright
username = os.getenv('GEOYOUNG_USER_ID')
password = os.getenv('GEOYOUNG_PASSWORD')
if not username or not password:
print("❌ GEOYOUNG_USER_ID, GEOYOUNG_PASSWORD 환경변수 필요")
return
print("=" * 60)
print("지오영 '전체삭제' 버튼 분석")
print("=" * 60)
async with async_playwright() as p:
browser = await p.chromium.launch(headless=False) # 보이게
page = await browser.new_page()
# 네트워크 요청 감시
requests_log = []
def log_request(request):
if 'Cart' in request.url or 'cart' in request.url or 'del' in request.url.lower():
requests_log.append({
'method': request.method,
'url': request.url,
'post_data': request.post_data
})
print(f"🔗 {request.method} {request.url}")
if request.post_data:
print(f" POST data: {request.post_data}")
page.on('request', log_request)
# 1. 로그인
print("\n1⃣ 로그인 중...")
await page.goto("https://gwn.geoweb.kr/Member/Login")
await page.fill('input[type="text"]', username)
await page.fill('input[type="password"]', password)
await page.click('button[type="submit"], input[type="submit"], .btn-login')
await page.wait_for_load_state('networkidle', timeout=15000)
print("✅ 로그인 완료")
# 2. 주문 페이지로 이동 (장바구니 표시됨)
print("\n2⃣ 주문 페이지로 이동...")
await page.goto("https://gwn.geoweb.kr/Home/Order")
await page.wait_for_load_state('networkidle', timeout=15000)
# 3. 장바구니 확인
print("\n3⃣ 장바구니 확인...")
await asyncio.sleep(2)
# 스크린샷
await page.screenshot(path='geo_cart_before.png')
print("📸 스크린샷 저장: geo_cart_before.png")
# 4. "전체삭제" 버튼 찾기
print("\n4'전체삭제' 버튼 찾기...")
# 가능한 선택자들
selectors = [
'button:has-text("전체삭제")',
'a:has-text("전체삭제")',
'.btn-del-all',
'#btnDelAll',
'[onclick*="delAll"]',
'button:has-text("전체 삭제")',
'button:has-text("삭제")',
]
delete_btn = None
for sel in selectors:
try:
btn = page.locator(sel).first
if await btn.count() > 0:
print(f" ✅ 버튼 발견: {sel}")
# 버튼의 HTML 확인
btn_html = await btn.evaluate('el => el.outerHTML')
print(f" HTML: {btn_html[:200]}...")
delete_btn = btn
break
except:
pass
if not delete_btn:
print(" ❌ 전체삭제 버튼을 찾지 못함")
# 페이지 HTML에서 삭제 관련 요소 검색
html = await page.content()
if '전체삭제' in html or 'delAll' in html:
print(" ⚠️ 페이지에 관련 텍스트는 있음. 수동 확인 필요")
await browser.close()
return
# 5. 버튼 클릭 (네트워크 요청 감시)
print("\n5'전체삭제' 버튼 클릭...")
requests_log.clear()
try:
await delete_btn.click()
await asyncio.sleep(2)
# confirm 다이얼로그 처리
page.on('dialog', lambda dialog: asyncio.create_task(dialog.accept()))
await page.wait_for_load_state('networkidle', timeout=5000)
except Exception as e:
print(f" ⚠️ 클릭 중 오류: {e}")
# 6. 캡처된 요청 출력
print("\n6⃣ 캡처된 API 요청:")
for req in requests_log:
print(f" 📤 {req['method']} {req['url']}")
if req['post_data']:
print(f" Body: {req['post_data']}")
# 스크린샷
await page.screenshot(path='geo_cart_after.png')
print("\n📸 스크린샷 저장: geo_cart_after.png")
print("\n잠시 대기 (확인용)...")
await asyncio.sleep(5)
await browser.close()
print("\n✅ 완료")
if __name__ == "__main__":
asyncio.run(analyze_delete_all())

View File

@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
"""
지오영 clear_cart (개선된 버전) 테스트
"""
import sys
sys.path.insert(0, r'c:\Users\청춘약국\source\pharmacy-wholesale-api')
# 싱글톤 초기화 강제
import importlib
import wholesale.geoyoung
importlib.reload(wholesale.geoyoung)
from dotenv import load_dotenv
load_dotenv(r'c:\Users\청춘약국\source\pharmacy-wholesale-api\.env')
from wholesale.geoyoung import GeoYoungSession
def test_clear_cart_new():
# 싱글톤 리셋
GeoYoungSession._instance = None
session = GeoYoungSession()
print("=" * 60)
print("지오영 clear_cart (개선된 버전) 테스트")
print("=" * 60)
# 1. 로그인
if not session.login():
print("❌ 로그인 실패")
return
print("✅ 로그인 성공\n")
# 2. 제품 추가 (테스트용)
print("📦 테스트용 제품 추가 중...")
add_result = session.add_to_cart('033133', 1) # 코자르탄
print(f" 결과: {add_result}")
import time
time.sleep(1)
# 3. 장바구니 확인
print("\n📦 [BEFORE] 장바구니:")
cart = session.get_cart()
print(f" 품목 수: {cart.get('total_items')}")
for item in cart.get('items', []):
print(f"{item.get('product_name')} (code: {item.get('product_code')})")
if not cart.get('items'):
print(" ⚠️ 장바구니 비어있음. 제품 추가 실패")
return
# 4. clear_cart 호출
print("\n🗑️ clear_cart() 호출 (개선된 버전)...")
clear_result = session.clear_cart()
print(f" 결과: {clear_result}")
# 5. 다시 확인
time.sleep(1)
print("\n📦 [AFTER] 장바구니:")
cart_after = session.get_cart()
print(f" 품목 수: {cart_after.get('total_items')}")
if not cart_after.get('items'):
print("\n✅ clear_cart 성공!")
else:
print(f"\n❌ clear_cart 실패! 아직 {len(cart_after.get('items', []))}개 남아있음")
if __name__ == "__main__":
test_clear_cart_new()

13
backend/test_geo_debug.py Normal file
View File

@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import GeoYoungSession
import json
g = GeoYoungSession()
g.login()
r = g.search_products('라식스')
if r.get('items'):
item = r['items'][0]
print("첫 번째 품목 전체 데이터:")
print(json.dumps(item, indent=2, ensure_ascii=False, default=str))

View File

@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
"""
지오영 개별 삭제 API 테스트
"""
import sys
sys.path.insert(0, r'c:\Users\청춘약국\source\pharmacy-wholesale-api')
from dotenv import load_dotenv
load_dotenv(r'c:\Users\청춘약국\source\pharmacy-wholesale-api\.env')
from wholesale.geoyoung import GeoYoungSession
def test_delete_item():
session = GeoYoungSession()
print("=" * 60)
print("지오영 개별 삭제(cancel_item) API 테스트")
print("=" * 60)
# 1. 로그인
if not session.login():
print("❌ 로그인 실패")
return
print("✅ 로그인 성공\n")
# 2. 현재 장바구니 조회
print("📦 [BEFORE] 장바구니:")
cart = session.get_cart()
print(f" 품목 수: {cart.get('total_items')}")
items = cart.get('items', [])
if not items:
print(" ⚠️ 장바구니 비어있음")
return
for item in items:
print(f"{item.get('product_name')} (code: {item.get('product_code')})")
# 3. 첫 번째 항목 삭제
first_item = items[0]
product_code = first_item.get('product_code')
print(f"\n🗑️ 첫 번째 항목 삭제 시도: {product_code}")
del_result = session.cancel_item(product_code=product_code)
print(f" 결과: {del_result}")
# 4. 다시 장바구니 조회
import time
time.sleep(1)
print("\n📦 [AFTER] 장바구니:")
cart_after = session.get_cart()
print(f" 품목 수: {cart_after.get('total_items')}")
for item in cart_after.get('items', []):
print(f"{item.get('product_name')} (code: {item.get('product_code')})")
if len(cart_after.get('items', [])) < len(items):
print("\n✅ cancel_item 성공!")
else:
print("\n❌ cancel_item 실패!")
if __name__ == "__main__":
test_delete_item()

145
backend/test_geo_html.py Normal file
View File

@@ -0,0 +1,145 @@
# -*- coding: utf-8 -*-
"""
지오영 장바구니 HTML 분석 및 삭제 버튼 API 캡처
"""
import asyncio
import os
import re
from dotenv import load_dotenv
load_dotenv(r'c:\Users\청춘약국\source\pharmacy-wholesale-api\.env')
async def analyze_cart_html():
from playwright.async_api import async_playwright
username = os.getenv('GEOYOUNG_USER_ID')
password = os.getenv('GEOYOUNG_PASSWORD')
print("=" * 60)
print("지오영 장바구니 HTML 분석")
print("=" * 60)
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await browser.new_context()
page = await context.new_page()
# 1. 로그인
print("\n1⃣ 로그인 중...")
await page.goto("https://gwn.geoweb.kr/Member/Login")
await page.wait_for_load_state('networkidle')
# 로그인 폼 찾기
await page.fill('input[type="text"], input[name*="id"], #userId', username)
await page.fill('input[type="password"], input[name*="pw"], #userPwd', password)
# 로그인 버튼 클릭
login_btns = ['button[type="submit"]', 'input[type="submit"]', '.btn-login', 'button:has-text("로그인")']
for btn_sel in login_btns:
try:
btn = page.locator(btn_sel).first
if await btn.count() > 0:
await btn.click()
break
except:
pass
await asyncio.sleep(3)
await page.wait_for_load_state('networkidle')
# 로그인 확인
if 'Login' in page.url:
print("❌ 로그인 실패")
await browser.close()
return
print(f"✅ 로그인 성공 (URL: {page.url})")
# 2. 장바구니 HTML 가져오기 (AJAX)
print("\n2⃣ 장바구니 HTML 가져오기...")
# PartialProductCart API 직접 호출
cart_response = await page.evaluate('''
async () => {
const response = await fetch('/Home/PartialProductCart', {
method: 'POST',
headers: {'X-Requested-With': 'XMLHttpRequest'}
});
return await response.text();
}
''')
print(f"\n📦 장바구니 HTML 길이: {len(cart_response)} bytes")
# 삭제 관련 키워드 찾기
patterns = [
r'onclick="[^"]*del[^"]*"',
r'onclick="[^"]*Del[^"]*"',
r'onclick="[^"]*삭제[^"]*"',
r'class="[^"]*del[^"]*"',
r'id="[^"]*del[^"]*"',
r'function\s+\w*[dD]el\w*\s*\(',
r'전체\s*삭제',
r'delAll',
r'deleteAll',
]
print("\n🔍 삭제 관련 패턴 검색:")
for pattern in patterns:
matches = re.findall(pattern, cart_response, re.IGNORECASE)
if matches:
for m in matches[:3]: # 최대 3개만
print(f"{pattern}: {m[:100]}...")
# 버튼 요소 찾기
print("\n🔘 버튼 요소:")
button_pattern = r'<button[^>]*>.*?</button>|<a[^>]*class="[^"]*btn[^"]*"[^>]*>.*?</a>'
buttons = re.findall(button_pattern, cart_response, re.DOTALL | re.IGNORECASE)
for btn in buttons[:10]:
clean_btn = re.sub(r'\s+', ' ', btn)[:150]
print(f"{clean_btn}")
# JavaScript 함수 찾기
print("\n📜 JavaScript 함수 (del/remove 관련):")
js_pattern = r'function\s+(\w*[dD]el\w*|\w*[rR]emove\w*)\s*\([^)]*\)\s*\{[^}]*\}'
js_funcs = re.findall(js_pattern, cart_response)
for func in js_funcs[:5]:
print(f"{func}")
# 전체 스크립트 태그에서 DataCart 관련 찾기
print("\n🔧 DataCart API 호출 패턴:")
datacart_pattern = r'DataCart[^"\']*'
datacart_matches = re.findall(datacart_pattern, cart_response)
for m in set(datacart_matches):
print(f"{m}")
# 페이지 전체 HTML에서도 검색
print("\n3⃣ 메인 페이지에서 추가 검색...")
await page.goto("https://gwn.geoweb.kr/Home/Order")
await page.wait_for_load_state('networkidle')
await asyncio.sleep(2)
full_html = await page.content()
# DataCart 관련 전체 검색
print("\n🔧 전체 페이지 DataCart API:")
datacart_all = re.findall(r'/Home/DataCart/\w+', full_html)
for api in set(datacart_all):
print(f"{api}")
# 삭제 함수 찾기
print("\n📜 삭제 관련 함수:")
del_funcs = re.findall(r'function\s+(\w*[dD]el\w*)\s*\(', full_html)
for func in set(del_funcs):
print(f"{func}()")
# 삭제 onclick 찾기
print("\n🖱️ 삭제 onclick:")
del_onclick = re.findall(r'onclick="([^"]*[dD]el[^"]*)"', full_html)
for onclick in set(del_onclick)[:5]:
print(f"{onclick[:100]}")
await browser.close()
print("\n✅ 분석 완료")
if __name__ == "__main__":
asyncio.run(analyze_cart_html())

View File

@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import GeoYoungSession
g = GeoYoungSession()
g.login()
for keyword in ['라식스', '코자정', '아스피린']:
r = g.search_products(keyword)
print(f"\n{keyword} 검색:")
if r.get('items'):
for item in r['items'][:2]:
print(f" {item['name'][:30]} | code: {item.get('product_code', '?')}")
else:
print(" 없음")

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import GeoYoungSession
g = GeoYoungSession()
g.login()
for kw in ['베아제', '신신파스', '마그밀', '활명수', '트라스트', '카베진']:
r = g.search_products(kw)
if r.get('items'):
item = r['items'][0]
print(f"{kw}: {item['name'][:30]} (code: {item.get('internal_code')})")
else:
print(f"{kw}: 없음")

View File

@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
"""지오영 선별 주문 테스트"""
import sys; sys.path.insert(0, '.'); import wholesale_path
import importlib
import wholesale.geoyoung
importlib.reload(wholesale.geoyoung)
from wholesale import GeoYoungSession
GeoYoungSession._instance = None
g = GeoYoungSession()
g.login()
g.clear_cart()
# 재고 있는 품목 검색
print('=== 1. 재고 확인 ===')
r1 = g.search_products('라식스')
r2 = g.search_products('코자정')
if not r1.get('items') or not r2.get('items'):
print('품목을 찾을 수 없습니다')
exit()
p1 = r1['items'][0]
p2 = r2['items'][0]
print(f"라식스: {p1['name']}, 재고 {p1.get('stock', '?')}, code: {p1['internal_code']}")
print(f"코자정: {p2['name']}, 재고 {p2.get('stock', '?')}, code: {p2['internal_code']}")
# 기존 품목 담기 (라식스 - 나중에 복원할 것)
print('\n=== 2. 기존 품목 (라식스) 담기 ===')
g.add_to_cart(p1['internal_code'], quantity=1)
# 새 품목 담기 (코자정 - 주문할 것)
print('=== 3. 새 품목 (코자정) 담기 ===')
g.add_to_cart(p2['internal_code'], quantity=1)
cart = g.get_cart()
print(f"현재 장바구니: {cart['total_items']}")
for item in cart['items']:
code = item.get('product_code') or item.get('internal_code', '?')
print(f" - {item['product_name'][:30]} (code: {code})")
# === 선별 주문 ===
print('\n' + '='*50)
print('=== 코자정만 주문! ===')
print('='*50)
# 코자정의 internal_code만 전달
print(f"\n주문할 internal_code: [{p2['internal_code']}]")
result = g.submit_order_selective([p2['internal_code']])
print(f"결과: {result}")
# 최종 확인
final = g.get_cart()
print(f"\n=== 최종 장바구니: {final['total_items']}개 ===")
for item in final['items']:
print(f" - {item['product_name'][:30]}")
if final['total_items'] == 1 and '라식스' in final['items'][0]['product_name']:
print('\n🎉 성공! 코자정만 주문됨, 라식스 복원됨!')
elif final['total_items'] == 0:
print('\n⚠️ 둘 다 주문됨 - 선별 주문 실패')
else:
print(f'\n🤔 예상 외 결과: {final["total_items"]}개 남음')

View File

@@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
"""지오영 선별 주문 최종 테스트"""
import sys; sys.path.insert(0, '.'); import wholesale_path
import importlib
import wholesale.geoyoung
importlib.reload(wholesale.geoyoung)
from wholesale import GeoYoungSession
GeoYoungSession._instance = None
g = GeoYoungSession()
g.login()
# 기존 장바구니 확인
print('=== 0. 기존 장바구니 확인 ===')
cart0 = g.get_cart()
print(f"기존 품목: {cart0['total_items']}")
for item in cart0['items']:
print(f" - {item['product_name'][:30]} (code: {item.get('product_code')})")
existing_codes = [item.get('product_code') for item in cart0['items']]
# 새 품목 담기 (디아맥스)
print('\n=== 1. 새 품목 (비타민D) 담기 ===')
result = g.full_order(
kd_code='썬비타민',
quantity=1,
specification=None,
check_stock=True,
auto_confirm=False # 장바구니만
)
print(f"결과: {result.get('success')}, {result.get('message', result.get('error'))}")
new_code = result.get('product', {}).get('internal_code') if result.get('success') else None
print(f"새 품목 internal_code: {new_code}")
# 장바구니 확인
print('\n=== 2. 장바구니 확인 ===')
cart1 = g.get_cart()
print(f"현재 품목: {cart1['total_items']}")
for item in cart1['items']:
print(f" - {item['product_name'][:30]} (code: {item.get('product_code')})")
# 선별 주문: 새 품목만!
if new_code:
print('\n=== 3. 선별 주문 (새 품목만!) ===')
print(f"주문할 코드: [{new_code}]")
confirm_result = g.submit_order_selective([new_code])
print(f"결과: {confirm_result}")
# 최종 장바구니 확인
print('\n=== 4. 최종 장바구니 ===')
cart2 = g.get_cart()
print(f"남은 품목: {cart2['total_items']}")
for item in cart2['items']:
print(f" - {item['product_name'][:30]}")
# 기존 품목이 모두 남아있는지 확인
remaining_codes = [item.get('product_code') for item in cart2['items']]
preserved = all(code in remaining_codes for code in existing_codes if code)
if preserved and cart2['total_items'] == len(existing_codes):
print('\n🎉 성공! 새 품목만 주문됨, 기존 품목 모두 복원!')
elif cart2['total_items'] == 0:
print('\n⚠️ 모든 품목 주문됨 - 선별 주문 실패')
else:
print(f'\n🤔 예상 외 결과')
else:
print('\n❌ 새 품목 담기 실패')

View File

@@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
"""지오영 선별 주문 최종 테스트 2"""
import sys; sys.path.insert(0, '.'); import wholesale_path
import importlib
import wholesale.geoyoung
importlib.reload(wholesale.geoyoung)
from wholesale import GeoYoungSession
GeoYoungSession._instance = None
g = GeoYoungSession()
g.login()
# 기존 장바구니 확인
print('=== 0. 기존 장바구니 확인 ===')
cart0 = g.get_cart()
print(f"기존 품목: {cart0['total_items']}")
for item in cart0['items']:
print(f" - {item['product_name'][:30]} (code: {item.get('product_code')})")
existing_codes = [item.get('product_code') for item in cart0['items']]
# 새 품목 담기 (타이레놀)
print('\n=== 1. 새 품목 (게보린) 담기 ===')
result = g.full_order(
kd_code='게보린',
quantity=1,
specification=None,
check_stock=True,
auto_confirm=False # 장바구니만
)
print(f"결과: {result.get('success')}, {result.get('message', result.get('error'))}")
new_code = result.get('product', {}).get('internal_code') if result.get('success') else None
print(f"새 품목 internal_code: {new_code}")
if not new_code:
# 다른 품목 시도
print('\n=== 1-2. 다른 품목 (판피린) 시도 ===')
result = g.full_order(
kd_code='판피린',
quantity=1,
specification=None,
check_stock=True,
auto_confirm=False
)
print(f"결과: {result.get('success')}, {result.get('message', result.get('error'))}")
new_code = result.get('product', {}).get('internal_code') if result.get('success') else None
# 장바구니 확인
print('\n=== 2. 장바구니 확인 ===')
cart1 = g.get_cart()
print(f"현재 품목: {cart1['total_items']}")
for item in cart1['items']:
print(f" - {item['product_name'][:30]} (code: {item.get('product_code')})")
# 선별 주문: 새 품목만!
if new_code:
print('\n=== 3. 선별 주문 (새 품목만!) ===')
print(f"주문할 코드: [{new_code}]")
confirm_result = g.submit_order_selective([new_code])
print(f"결과: {confirm_result}")
# 최종 장바구니 확인
print('\n=== 4. 최종 장바구니 ===')
cart2 = g.get_cart()
print(f"남은 품목: {cart2['total_items']}")
for item in cart2['items']:
print(f" - {item['product_name'][:30]}")
# 기존 품목이 모두 남아있는지 확인
remaining_codes = [item.get('product_code') for item in cart2['items']]
preserved = all(code in remaining_codes for code in existing_codes if code)
if preserved and cart2['total_items'] == len(existing_codes):
print('\n🎉 성공! 새 품목만 주문됨, 기존 품목 모두 복원!')
elif cart2['total_items'] == 0:
print('\n⚠️ 모든 품목 주문됨 - 선별 주문 실패')
else:
print(f'\n🤔 결과: 기존 {len(existing_codes)}개 중 {len([c for c in existing_codes if c in remaining_codes])}개 복원')
else:
print('\n❌ 새 품목 담기 실패')

View File

@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
"""지오영 선별 주문 최종 테스트 - 마그밀"""
import sys; sys.path.insert(0, '.'); import wholesale_path
import importlib
import wholesale.geoyoung
importlib.reload(wholesale.geoyoung)
from wholesale import GeoYoungSession
GeoYoungSession._instance = None
g = GeoYoungSession()
g.login()
# 기존 장바구니 확인
print('=== 0. 기존 장바구니 확인 ===')
cart0 = g.get_cart()
print(f"기존 품목: {cart0['total_items']}")
for item in cart0['items']:
print(f" - {item['product_name'][:30]} (code: {item.get('product_code')})")
existing_count = cart0['total_items']
existing_codes = [item.get('product_code') for item in cart0['items']]
# 새 품목 담기 (마그밀)
print('\n=== 1. 새 품목 (마그밀) 담기 ===')
result = g.full_order(
kd_code='마그밀',
quantity=1,
specification=None,
check_stock=True,
auto_confirm=False # 장바구니만
)
print(f"결과: {result.get('success')}, {result.get('message', result.get('error'))}")
new_code = result.get('product', {}).get('internal_code') if result.get('success') else None
print(f"새 품목 internal_code: {new_code}")
# 장바구니 확인
print('\n=== 2. 장바구니 확인 ===')
cart1 = g.get_cart()
print(f"현재 품목: {cart1['total_items']}")
for item in cart1['items']:
print(f" - {item['product_name'][:30]} (code: {item.get('product_code')})")
# 선별 주문: 새 품목만!
if new_code:
print('\n=== 3. 선별 주문 (마그밀만!) ===')
print(f"주문할 코드: [{new_code}]")
confirm_result = g.submit_order_selective([new_code])
print(f"결과: {confirm_result}")
# 최종 장바구니 확인
print('\n=== 4. 최종 장바구니 ===')
cart2 = g.get_cart()
print(f"남은 품목: {cart2['total_items']}")
for item in cart2['items']:
print(f" - {item['product_name'][:30]}")
# 기존 품목이 모두 남아있는지 확인
remaining_codes = [item.get('product_code') for item in cart2['items']]
preserved_count = len([c for c in existing_codes if c in remaining_codes])
if cart2['total_items'] == existing_count and preserved_count == existing_count:
print(f'\n🎉 성공! 마그밀만 주문됨, 기존 {existing_count}개 품목 모두 복원!')
elif cart2['total_items'] == 0:
print('\n⚠️ 모든 품목 주문됨 - 선별 주문 실패')
else:
print(f'\n🤔 결과: 기존 {existing_count}개 중 {preserved_count}개 복원, 현재 {cart2["total_items"]}')
else:
print('\n❌ 새 품목 담기 실패')

View File

@@ -1,112 +0,0 @@
# -*- coding: utf-8 -*-
"""지오영 API 직접 테스트"""
import asyncio
from playwright.async_api import async_playwright
import json
async def capture_cart_api():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
# 요청/응답 캡처
cart_requests = []
async def handle_request(request):
if 'Cart' in request.url or 'Order' in request.url or 'Add' in request.url:
cart_requests.append({
'url': request.url,
'method': request.method,
'headers': dict(request.headers),
'data': request.post_data
})
page.on('request', handle_request)
# 로그인
await page.goto('https://gwn.geoweb.kr/Member/Login')
await page.fill('input[type="text"]', '7390')
await page.fill('input[type="password"]', 'trajet6640')
await page.click('button, input[type="submit"]')
await page.wait_for_load_state('networkidle')
print("로그인 완료")
# 쿠키 저장
cookies = await page.context.cookies()
print(f"쿠키: {[c['name'] for c in cookies]}")
# 검색 페이지
await page.goto('https://gwn.geoweb.kr/Home/Index')
await page.wait_for_timeout(2000)
# 검색 (AJAX)
await page.evaluate('''
$.ajax({
url: "/Home/PartialSearchProduct",
type: "POST",
data: {srchText: "643104281"},
success: function(data) {
console.log("검색 결과:", data.substring(0, 500));
}
});
''')
await page.wait_for_timeout(2000)
# 장바구니 추가 시도 (JavaScript로)
result = await page.evaluate('''
async function testCart() {
// 장바구니 추가 함수 찾기
if (typeof AddCart !== 'undefined') {
return "AddCart 함수 존재";
}
if (typeof fnAddCart !== 'undefined') {
return "fnAddCart 함수 존재";
}
// 전역 함수 목록
var funcs = [];
for (var key in window) {
if (typeof window[key] === 'function' &&
(key.toLowerCase().includes('cart') ||
key.toLowerCase().includes('order') ||
key.toLowerCase().includes('add'))) {
funcs.push(key);
}
}
return "발견된 함수: " + funcs.join(", ");
}
return testCart();
''')
print(f"JavaScript 분석: {result}")
# 페이지 소스에서 장바구니 관련 스크립트 찾기
scripts = await page.evaluate('''
var scripts = document.querySelectorAll('script');
var result = [];
scripts.forEach(function(s) {
var text = s.textContent || s.innerText || '';
if (text.includes('Cart') || text.includes('AddProduct')) {
result.push(text.substring(0, 1000));
}
});
return result;
''')
await browser.close()
print("\n" + "="*60)
print("캡처된 Cart/Order 요청:")
print("="*60)
for r in cart_requests:
print(json.dumps(r, indent=2, ensure_ascii=False))
print("\n" + "="*60)
print("장바구니 관련 스크립트:")
print("="*60)
for i, s in enumerate(scripts[:3]):
print(f"\n--- Script {i+1} ---")
print(s[:800])
if __name__ == "__main__":
asyncio.run(capture_cart_api())

View File

@@ -1,98 +0,0 @@
"""
통합 테스트: QR 라벨 전체 흐름
토큰 생성 → DB 저장 → QR 라벨 이미지 생성
"""
import sys
import os
from datetime import datetime
# Path setup
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
from utils.qr_token_generator import generate_claim_token, save_token_to_db
from utils.qr_label_printer import print_qr_label
def test_full_flow():
"""전체 흐름 테스트"""
# 1. 테스트 데이터 (새로운 거래 ID)
test_tx_id = datetime.now().strftime("TEST%Y%m%d%H%M%S")
test_amount = 75000.0
test_time = datetime.now()
print("=" * 80)
print("QR 라벨 통합 테스트")
print("=" * 80)
print(f"거래 ID: {test_tx_id}")
print(f"판매 금액: {test_amount:,}")
print()
# 2. 토큰 생성
print("[1/3] Claim Token 생성...")
token_info = generate_claim_token(test_tx_id, test_amount)
print(f" [OK] 토큰 원문: {token_info['token_raw'][:50]}...")
print(f" [OK] 토큰 해시: {token_info['token_hash'][:32]}...")
print(f" [OK] QR URL: {token_info['qr_url']}")
print(f" [OK] URL 길이: {len(token_info['qr_url'])} 문자")
print(f" [OK] 적립 포인트: {token_info['claimable_points']}P")
print()
# 3. DB 저장
print("[2/3] SQLite DB 저장...")
success, error = save_token_to_db(
test_tx_id,
token_info['token_hash'],
test_amount,
token_info['claimable_points'],
token_info['expires_at'],
token_info['pharmacy_id']
)
if not success:
print(f" [ERROR] DB 저장 실패: {error}")
return False
print(f" [OK] DB 저장 성공")
print()
# 4. QR 라벨 생성 (미리보기 모드)
print("[3/3] QR 라벨 이미지 생성...")
success, image_path = print_qr_label(
token_info['qr_url'],
test_tx_id,
test_amount,
token_info['claimable_points'],
test_time,
preview_mode=True
)
if not success:
print(f" [ERROR] 이미지 생성 실패")
return False
print(f" [OK] 이미지 저장: {image_path}")
print()
# 5. 결과 요약
print("=" * 80)
print("[SUCCESS] 통합 테스트 성공!")
print("=" * 80)
print(f"QR URL: {token_info['qr_url']}")
print(f"이미지 파일: {image_path}")
print(f"\n다음 명령으로 확인:")
print(f" start {image_path}")
print("=" * 80)
return True
if __name__ == "__main__":
try:
success = test_full_flow()
sys.exit(0 if success else 1)
except Exception as e:
print(f"\n[ERROR] 테스트 실패: {e}")
import traceback
traceback.print_exc()
sys.exit(1)

76
backend/test_order_end.py Normal file
View File

@@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import SooinSession
from bs4 import BeautifulSoup
import re
s = SooinSession()
s.login()
s.clear_cart()
result = s.search_products('코자정')
product = result['items'][0]
s.add_to_cart(product['internal_code'], qty=1, price=product['price'], stock=product['stock'])
resp = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
soup = BeautifulSoup(resp.content, 'html.parser')
form = soup.find('form', {'id': 'frmBag'})
# form action 확인
form_action = form.get('action', '')
print(f'form action: {form_action}')
# 올바른 URL 구성
ORDER_END_URL = 'http://sooinpharm.co.kr/Service/Order/OrderEnd.asp'
form_data = {}
for inp in form.find_all('input'):
name = inp.get('name', '')
if not name: continue
inp_type = inp.get('type', '').lower()
if inp_type == 'checkbox':
form_data[name] = 'on' # 체크박스 선택
else:
form_data[name] = inp.get('value', '')
# x, y 좌표 (image input 클릭)
form_data['x'] = '10'
form_data['y'] = '10'
print(f"chk_0: {form_data.get('chk_0')}")
print(f"kind: {form_data.get('kind')}")
print(f'\nPOST to: {ORDER_END_URL}')
resp = s.session.post(
ORDER_END_URL,
data=form_data,
headers={
'Content-Type': 'application/x-www-form-urlencoded',
'Referer': f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}'
},
timeout=30
)
print(f'응답 상태: {resp.status_code}')
print(f'응답 길이: {len(resp.text)}')
# alert 확인
alert_match = re.search(r'alert\("([^"]*)"\)', resp.text)
alert_msg = alert_match.group(1) if alert_match else 'N/A'
print(f'alert 메시지: "{alert_msg}"')
# 응답 일부 출력
print('\n응답 앞부분:')
print(resp.text[:1000])
# 장바구니 확인
resp2 = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
soup2 = BeautifulSoup(resp2.content, 'html.parser')
int_array = soup2.find('input', {'name': 'intArray'})
val = int_array.get('value') if int_array else '없음'
print(f'\n주문 후 intArray: {val}')
if val == '-1':
print('\n🎉 주문 성공!')
else:
print('\n❌ 주문 실패')

View File

@@ -1,8 +0,0 @@
from sqlalchemy import create_engine, text
pg_engine = create_engine('postgresql://admin:trajet6640@192.168.0.87:5432/apdb_master')
with pg_engine.connect() as conn:
result = conn.execute(text("SELECT apc, product_name, company_name, main_ingredient FROM apc WHERE product_name LIKE '%아시엔로%' LIMIT 20"))
print('아시엔로 검색 결과:')
for row in result:
print(f' APC: {row[0]} | {row[1]} | {row[2]} | {row[3]}')

69
backend/test_post_data.py Normal file
View File

@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
"""실제 POST 데이터 확인"""
import sys; sys.path.insert(0, '.'); import wholesale_path
from bs4 import BeautifulSoup
import re
import importlib
import wholesale.sooin
importlib.reload(wholesale.sooin)
from wholesale import SooinSession
SooinSession._instance = None
s = SooinSession()
s.login()
s.clear_cart()
# 2개 품목 담기
r1 = s.search_products('코자정')
s.add_to_cart(r1['items'][0]['internal_code'], qty=1, price=r1['items'][0]['price'], stock=r1['items'][0]['stock'])
r2 = s.search_products('디카맥스')
s.add_to_cart(r2['items'][0]['internal_code'], qty=1, price=r2['items'][0]['price'], stock=r2['items'][0]['stock'])
# row 0 취소 (디카맥스)
s.cancel_item(row_index=0)
# Bag.asp GET
resp = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
soup = BeautifulSoup(resp.content, 'html.parser')
form = soup.find('form', {'id': 'frmBag'})
form_data = {}
for inp in form.find_all('input'):
name = inp.get('name', '')
if not name:
continue
inp_type = inp.get('type', 'text').lower()
if inp_type == 'checkbox':
if inp.get('checked') is not None:
form_data[name] = 'on'
continue
form_data[name] = inp.get('value', '')
form_data['kind'] = 'order'
form_data['tx_memo'] = '선별 테스트'
print('=== POST할 데이터 (체크박스 관련) ===')
for k, v in form_data.items():
if 'chk' in k.lower():
print(f" {k}: {v}")
print(f"\n=== 실제 POST ===")
resp = s.session.post(
s.ORDER_END_URL,
data=form_data,
timeout=30
)
alert_match = re.search(r'alert\("([^"]*)"\)', resp.text)
alert_msg = alert_match.group(1) if alert_match else 'N/A'
print(f"응답: {alert_msg}")
# 장바구니 확인
cart = s.get_cart()
print(f"\n남은 품목: {cart['total_items']}")
for item in cart['items']:
print(f" - {item['product_name']}")

View File

@@ -1,298 +0,0 @@
"""
ESC/POS QR 코드 인쇄 방식 테스트
여러 가지 방법을 한 번에 시도하여 어떤 방식이 작동하는지 확인
"""
import socket
import qrcode
import time
from PIL import Image
# 프린터 설정 (고정)
PRINTER_IP = "192.168.0.174"
PRINTER_PORT = 9100
# 테스트 URL (짧은 버전)
TEST_URL = "https://mile.0bin.in/test"
def send_to_printer(data, method_name):
"""프린터로 데이터 전송"""
try:
print(f"\n{'='*60}")
print(f"[{method_name}] 전송 시작...")
print(f"데이터 크기: {len(data)} bytes")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(10)
sock.connect((PRINTER_IP, PRINTER_PORT))
sock.sendall(data)
sock.close()
print(f"[{method_name}] ✅ 전송 완료!")
time.sleep(2) # 프린터 처리 대기
return True
except Exception as e:
print(f"[{method_name}] ❌ 실패: {e}")
return False
def method_1_native_qr_model2():
"""
방법 1: 프린터 내장 QR 생성 (GS ( k) - Model 2
가장 안정적이지만 프린터 지원 필요
"""
ESC = b'\x1b'
GS = b'\x1d'
commands = []
# 초기화
commands.append(ESC + b'@')
# 헤더
commands.append("\n".encode('euc-kr'))
commands.append("================================\n".encode('euc-kr'))
commands.append(" *** 방법 1 ***\n".encode('euc-kr'))
commands.append(" 내장 QR (GS ( k)\n".encode('euc-kr'))
commands.append("================================\n".encode('euc-kr'))
commands.append("\n".encode('euc-kr'))
# QR 설정
# GS ( k pL pH cn fn n (QR Code)
# cn = 49 (Model 1/2 선택)
# fn = 65 (모델 선택)
# n = 50 (Model 2)
# 모델 설정
commands.append(GS + b'(k' + bytes([3, 0, 49, 65, 50])) # Model 2
# 에러 정정 레벨 설정 (fn=69, n=48=L)
commands.append(GS + b'(k' + bytes([3, 0, 49, 69, 48]))
# 모듈 크기 설정 (fn=67, n=8)
commands.append(GS + b'(k' + bytes([3, 0, 49, 67, 8]))
# QR 데이터 저장 (fn=80)
qr_data = TEST_URL.encode('utf-8')
data_len = len(qr_data) + 3
pL = data_len & 0xFF
pH = (data_len >> 8) & 0xFF
commands.append(GS + b'(k' + bytes([pL, pH, 49, 80, 48]) + qr_data)
# QR 인쇄 (fn=81)
commands.append(GS + b'(k' + bytes([3, 0, 49, 81, 48]))
# 푸터
commands.append("\n".encode('euc-kr'))
commands.append(f"URL: {TEST_URL}\n".encode('euc-kr'))
commands.append("\n\n\n".encode('euc-kr'))
# 용지 커트
commands.append(GS + b'V' + bytes([1]))
return b''.join(commands)
def method_2_raster_bitmap_gs_v():
"""
방법 2: Raster Bit Image (GS v 0)
"""
ESC = b'\x1b'
GS = b'\x1d'
commands = []
# 초기화
commands.append(ESC + b'@')
# 헤더
commands.append("\n".encode('euc-kr'))
commands.append("================================\n".encode('euc-kr'))
commands.append(" *** 방법 2 ***\n".encode('euc-kr'))
commands.append(" Raster (GS v 0)\n".encode('euc-kr'))
commands.append("================================\n".encode('euc-kr'))
commands.append("\n".encode('euc-kr'))
# QR 이미지 생성 (작게: 80x80)
qr = qrcode.QRCode(version=1, box_size=2, border=2)
qr.add_data(TEST_URL)
qr.make(fit=True)
qr_image = qr.make_image(fill_color="black", back_color="white")
qr_image = qr_image.resize((80, 80))
# 1비트 흑백으로 변환
qr_image = qr_image.convert('1')
width, height = qr_image.size
pixels = qr_image.load()
# GS v 0 명령어
width_bytes = (width + 7) // 8
commands.append(GS + b'v0' + bytes([0])) # 보통 모드
commands.append(bytes([width_bytes & 0xFF, (width_bytes >> 8) & 0xFF])) # xL, xH
commands.append(bytes([height & 0xFF, (height >> 8) & 0xFF])) # yL, yH
# 이미지 데이터
for y in range(height):
for x in range(0, width, 8):
byte = 0
for bit in range(8):
if x + bit < width:
if pixels[x + bit, y] == 0: # 검은색
byte |= (1 << (7 - bit))
commands.append(bytes([byte]))
# 푸터
commands.append("\n".encode('euc-kr'))
commands.append(f"URL: {TEST_URL}\n".encode('euc-kr'))
commands.append("\n\n\n".encode('euc-kr'))
# 용지 커트
commands.append(GS + b'V' + bytes([1]))
return b''.join(commands)
def method_3_bit_image_esc_star():
"""
방법 3: Bit Image (ESC *) - 24-dot double-density
현재 사용 중인 방식
"""
ESC = b'\x1b'
GS = b'\x1d'
commands = []
# 초기화
commands.append(ESC + b'@')
# 헤더
commands.append("\n".encode('euc-kr'))
commands.append("================================\n".encode('euc-kr'))
commands.append(" *** 방법 3 ***\n".encode('euc-kr'))
commands.append(" Bit Image (ESC *)\n".encode('euc-kr'))
commands.append("================================\n".encode('euc-kr'))
commands.append("\n".encode('euc-kr'))
# QR 이미지 생성 (작게: 80x80)
qr = qrcode.QRCode(version=1, box_size=2, border=2)
qr.add_data(TEST_URL)
qr.make(fit=True)
qr_image = qr.make_image(fill_color="black", back_color="white")
qr_image = qr_image.resize((80, 80))
# 1비트 흑백으로 변환
qr_image = qr_image.convert('1')
width, height = qr_image.size
pixels = qr_image.load()
# ESC * 명령어로 라인별 인쇄
for y in range(0, height, 24):
line_height = min(24, height - y)
# ESC * m nL nH
nL = width & 0xFF
nH = (width >> 8) & 0xFF
commands.append(ESC + b'*' + bytes([33, nL, nH])) # m=33 (24-dot double-density)
# 라인 데이터
for x in range(width):
byte1, byte2, byte3 = 0, 0, 0
for bit in range(line_height):
pixel_y = y + bit
if pixel_y < height:
if pixels[x, pixel_y] == 0: # 검은색
if bit < 8:
byte1 |= (1 << (7 - bit))
elif bit < 16:
byte2 |= (1 << (15 - bit))
else:
byte3 |= (1 << (23 - bit))
commands.append(bytes([byte1, byte2, byte3]))
commands.append(b'\n')
# 푸터
commands.append("\n".encode('euc-kr'))
commands.append(f"URL: {TEST_URL}\n".encode('euc-kr'))
commands.append("\n\n\n".encode('euc-kr'))
# 용지 커트
commands.append(GS + b'V' + bytes([1]))
return b''.join(commands)
def method_4_simple_text_only():
"""
방법 4: 텍스트만 (비교용)
"""
ESC = b'\x1b'
GS = b'\x1d'
commands = []
# 초기화
commands.append(ESC + b'@')
# 헤더
commands.append("\n".encode('euc-kr'))
commands.append("================================\n".encode('euc-kr'))
commands.append(" *** 방법 4 ***\n".encode('euc-kr'))
commands.append(" 텍스트만 (비교용)\n".encode('euc-kr'))
commands.append("================================\n".encode('euc-kr'))
commands.append("\n".encode('euc-kr'))
commands.append("QR 이미지 대신 URL만 출력\n".encode('euc-kr'))
commands.append("\n".encode('euc-kr'))
commands.append(f"URL: {TEST_URL}\n".encode('euc-kr'))
commands.append("\n\n\n".encode('euc-kr'))
# 용지 커트
commands.append(GS + b'V' + bytes([1]))
return b''.join(commands)
def main():
"""메인 실행"""
print("="*60)
print("ESC/POS QR 코드 인쇄 방식 테스트")
print("="*60)
print(f"프린터: {PRINTER_IP}:{PRINTER_PORT}")
print(f"테스트 URL: {TEST_URL}")
print("="*60)
methods = [
("방법 1: 프린터 내장 QR (GS ( k)", method_1_native_qr_model2),
("방법 2: Raster Bitmap (GS v 0)", method_2_raster_bitmap_gs_v),
("방법 3: Bit Image (ESC *)", method_3_bit_image_esc_star),
("방법 4: 텍스트만", method_4_simple_text_only),
]
results = []
for name, method_func in methods:
try:
data = method_func()
success = send_to_printer(data, name)
results.append((name, success))
except Exception as e:
print(f"[{name}] ❌ 함수 실행 오류: {e}")
results.append((name, False))
# 결과 요약
print("\n" + "="*60)
print("테스트 결과 요약")
print("="*60)
for name, success in results:
status = "✅ 성공" if success else "❌ 실패"
print(f"{name}: {status}")
print("\n인쇄된 영수증을 확인하여 어떤 방법이 QR을 제대로 출력했는지 확인하세요!")
print("="*60)
if __name__ == "__main__":
main()

View File

@@ -1,281 +0,0 @@
"""
ESC/POS QR 코드 인쇄 방식 테스트 v2
더 많은 변형 시도 (크기, 밀도, 파라미터)
"""
import socket
import qrcode
import time
# 프린터 설정
PRINTER_IP = "192.168.0.174"
PRINTER_PORT = 9100
# 테스트 URL (더 짧게)
TEST_URL = "https://bit.ly/test"
def send_to_printer(data, method_name):
"""프린터로 데이터 전송"""
try:
print(f"\n[{method_name}] 전송 중... ({len(data)} bytes)")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(10)
sock.connect((PRINTER_IP, PRINTER_PORT))
sock.sendall(data)
sock.close()
print(f"[{method_name}] ✅ 완료")
time.sleep(1.5)
return True
except Exception as e:
print(f"[{method_name}] ❌ 실패: {e}")
return False
def method_1_tiny_qr_escstar():
"""방법 1: 아주 작은 QR (30x30) + ESC *"""
ESC = b'\x1b'
GS = b'\x1d'
commands = []
commands.append(ESC + b'@')
commands.append("\n================================\n".encode('euc-kr'))
commands.append(" *** 방법 1 ***\n".encode('euc-kr'))
commands.append(" 작은 QR 30x30 (ESC *)\n".encode('euc-kr'))
commands.append("================================\n\n".encode('euc-kr'))
# 30x30 QR
qr = qrcode.QRCode(version=1, box_size=1, border=1)
qr.add_data(TEST_URL)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white").resize((30, 30)).convert('1')
width, height = img.size
pixels = img.load()
# ESC * m=0 (8-dot single-density)
for y in range(0, height, 8):
commands.append(ESC + b'*' + bytes([0, width & 0xFF, (width >> 8) & 0xFF]))
for x in range(width):
byte = 0
for bit in range(min(8, height - y)):
if pixels[x, y + bit] == 0:
byte |= (1 << (7 - bit))
commands.append(bytes([byte]))
commands.append(b'\n')
commands.append(f"\nURL: {TEST_URL}\n\n\n".encode('euc-kr'))
commands.append(GS + b'V' + bytes([1]))
return b''.join(commands)
def method_2_medium_qr_escstar_mode32():
"""방법 2: 중간 QR (50x50) + ESC * mode 32"""
ESC = b'\x1b'
GS = b'\x1d'
commands = []
commands.append(ESC + b'@')
commands.append("\n================================\n".encode('euc-kr'))
commands.append(" *** 방법 2 ***\n".encode('euc-kr'))
commands.append(" 중간 QR 50x50 (ESC * m=32)\n".encode('euc-kr'))
commands.append("================================\n\n".encode('euc-kr'))
# 50x50 QR
qr = qrcode.QRCode(version=1, box_size=2, border=1)
qr.add_data(TEST_URL)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white").resize((50, 50)).convert('1')
width, height = img.size
pixels = img.load()
# ESC * m=32 (24-dot single-density)
for y in range(0, height, 24):
commands.append(ESC + b'*' + bytes([32, width & 0xFF, (width >> 8) & 0xFF]))
for x in range(width):
byte1 = byte2 = byte3 = 0
for bit in range(min(24, height - y)):
if pixels[x, y + bit] == 0:
if bit < 8:
byte1 |= (1 << (7 - bit))
elif bit < 16:
byte2 |= (1 << (15 - bit))
else:
byte3 |= (1 << (23 - bit))
commands.append(bytes([byte1, byte2, byte3]))
commands.append(b'\n')
commands.append(f"\nURL: {TEST_URL}\n\n\n".encode('euc-kr'))
commands.append(GS + b'V' + bytes([1]))
return b''.join(commands)
def method_3_native_qr_simple():
"""방법 3: 내장 QR (더 간단한 설정)"""
ESC = b'\x1b'
GS = b'\x1d'
commands = []
commands.append(ESC + b'@')
commands.append("\n================================\n".encode('euc-kr'))
commands.append(" *** 방법 3 ***\n".encode('euc-kr'))
commands.append(" 내장 QR 간단 설정\n".encode('euc-kr'))
commands.append("================================\n\n".encode('euc-kr'))
qr_data = TEST_URL.encode('utf-8')
data_len = len(qr_data) + 3
# Model 2
commands.append(GS + b'(k' + bytes([3, 0, 49, 65, 50]))
# Error correction L
commands.append(GS + b'(k' + bytes([3, 0, 49, 69, 48]))
# Size 4
commands.append(GS + b'(k' + bytes([3, 0, 49, 67, 4]))
# Store data
commands.append(GS + b'(k' + bytes([data_len & 0xFF, (data_len >> 8) & 0xFF, 49, 80, 48]) + qr_data)
# Print
commands.append(GS + b'(k' + bytes([3, 0, 49, 81, 48]))
commands.append(f"\n\nURL: {TEST_URL}\n\n\n".encode('euc-kr'))
commands.append(GS + b'V' + bytes([1]))
return b''.join(commands)
def method_4_native_qr_model1():
"""방법 4: 내장 QR Model 1 (구형)"""
ESC = b'\x1b'
GS = b'\x1d'
commands = []
commands.append(ESC + b'@')
commands.append("\n================================\n".encode('euc-kr'))
commands.append(" *** 방법 4 ***\n".encode('euc-kr'))
commands.append(" 내장 QR Model 1\n".encode('euc-kr'))
commands.append("================================\n\n".encode('euc-kr'))
qr_data = TEST_URL.encode('utf-8')
data_len = len(qr_data) + 3
# Model 1 (n=49)
commands.append(GS + b'(k' + bytes([3, 0, 49, 65, 49]))
# Error correction L
commands.append(GS + b'(k' + bytes([3, 0, 49, 69, 48]))
# Size 4
commands.append(GS + b'(k' + bytes([3, 0, 49, 67, 4]))
# Store data
commands.append(GS + b'(k' + bytes([data_len & 0xFF, (data_len >> 8) & 0xFF, 49, 80, 48]) + qr_data)
# Print
commands.append(GS + b'(k' + bytes([3, 0, 49, 81, 48]))
commands.append(f"\n\nURL: {TEST_URL}\n\n\n".encode('euc-kr'))
commands.append(GS + b'V' + bytes([1]))
return b''.join(commands)
def method_5_raster_tiny():
"""방법 5: Raster 초소형 (40x40)"""
ESC = b'\x1b'
GS = b'\x1d'
commands = []
commands.append(ESC + b'@')
commands.append("\n================================\n".encode('euc-kr'))
commands.append(" *** 방법 5 ***\n".encode('euc-kr'))
commands.append(" Raster 40x40 (GS v 0)\n".encode('euc-kr'))
commands.append("================================\n\n".encode('euc-kr'))
# 40x40 QR
qr = qrcode.QRCode(version=1, box_size=1, border=1)
qr.add_data(TEST_URL)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white").resize((40, 40)).convert('1')
width, height = img.size
pixels = img.load()
width_bytes = (width + 7) // 8
commands.append(GS + b'v0' + bytes([0]))
commands.append(bytes([width_bytes & 0xFF, (width_bytes >> 8) & 0xFF]))
commands.append(bytes([height & 0xFF, (height >> 8) & 0xFF]))
for y in range(height):
for x in range(0, width, 8):
byte = 0
for bit in range(8):
if x + bit < width and pixels[x + bit, y] == 0:
byte |= (1 << (7 - bit))
commands.append(bytes([byte]))
commands.append(f"\n\nURL: {TEST_URL}\n\n\n".encode('euc-kr'))
commands.append(GS + b'V' + bytes([1]))
return b''.join(commands)
def method_6_no_align():
"""방법 6: 정렬 없이 + 작은 QR"""
ESC = b'\x1b'
GS = b'\x1d'
commands = []
commands.append(ESC + b'@')
# 정렬 명령 없음!
commands.append("\n================================\n".encode('euc-kr'))
commands.append(" *** 방법 6 ***\n".encode('euc-kr'))
commands.append(" 정렬 없음 + QR 35x35\n".encode('euc-kr'))
commands.append("================================\n\n".encode('euc-kr'))
# 35x35 QR
qr = qrcode.QRCode(version=1, box_size=1, border=1)
qr.add_data(TEST_URL)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white").resize((35, 35)).convert('1')
width, height = img.size
pixels = img.load()
# ESC * m=1 (8-dot double-density)
for y in range(0, height, 8):
commands.append(ESC + b'*' + bytes([1, width & 0xFF, (width >> 8) & 0xFF]))
for x in range(width):
byte = 0
for bit in range(min(8, height - y)):
if pixels[x, y + bit] == 0:
byte |= (1 << (7 - bit))
commands.append(bytes([byte]))
commands.append(b'\n')
commands.append(f"\nURL: {TEST_URL}\n\n\n".encode('euc-kr'))
commands.append(GS + b'V' + bytes([1]))
return b''.join(commands)
def main():
print("="*60)
print("ESC/POS QR 테스트 v2 - 더 많은 변형")
print("="*60)
print(f"프린터: {PRINTER_IP}:{PRINTER_PORT}")
print(f"테스트 URL: {TEST_URL}")
print("="*60)
methods = [
("방법 1: 30x30 ESC * m=0", method_1_tiny_qr_escstar),
("방법 2: 50x50 ESC * m=32", method_2_medium_qr_escstar_mode32),
("방법 3: 내장 QR 간단", method_3_native_qr_simple),
("방법 4: 내장 QR Model 1", method_4_native_qr_model1),
("방법 5: 40x40 Raster", method_5_raster_tiny),
("방법 6: 정렬 없음 35x35", method_6_no_align),
]
results = []
for name, method_func in methods:
try:
data = method_func()
success = send_to_printer(data, name)
results.append((name, success))
except Exception as e:
print(f"[{name}] ❌ 오류: {e}")
results.append((name, False))
print("\n" + "="*60)
print("결과 요약")
print("="*60)
for name, success in results:
print(f"{name}: {'' if success else ''}")
print("\n6장의 영수증이 나옵니다. QR이 보이는 번호를 알려주세요!")
print("="*60)
if __name__ == "__main__":
main()

View File

@@ -1,263 +0,0 @@
"""
python-escpos 라이브러리를 사용한 QR 코드 인쇄 테스트
훨씬 더 간단하고 안정적!
설치: pip install python-escpos
"""
from escpos.printer import Network
from escpos import escpos
import time
# 프린터 설정
PRINTER_IP = "192.168.0.174"
PRINTER_PORT = 9100
# 테스트 URL
TEST_URL = "https://mile.0bin.in/test"
def test_method_1_native_qr():
"""방법 1: escpos 라이브러리 내장 QR 함수"""
print("\n" + "="*60)
print("방법 1: escpos.qr() - 프린터 내장 QR")
print("="*60)
try:
p = Network(PRINTER_IP, port=PRINTER_PORT)
p.text("\n")
p.text("================================\n")
p.text(" *** 방법 1 ***\n")
p.text(" escpos.qr() 내장\n")
p.text("================================\n")
p.text("\n")
# QR 코드 인쇄 (프린터 내장)
p.qr(TEST_URL, size=4, center=True)
p.text("\n")
p.text(f"URL: {TEST_URL}\n")
p.text("\n\n\n")
p.cut()
print("✅ 방법 1 성공!")
time.sleep(2)
return True
except Exception as e:
print(f"❌ 방법 1 실패: {e}")
import traceback
traceback.print_exc()
return False
def test_method_2_image():
"""방법 2: escpos 라이브러리 이미지 함수"""
print("\n" + "="*60)
print("방법 2: escpos.image() - QR을 이미지로 변환하여 인쇄")
print("="*60)
try:
import qrcode
from io import BytesIO
p = Network(PRINTER_IP, port=PRINTER_PORT)
p.text("\n")
p.text("================================\n")
p.text(" *** 방법 2 ***\n")
p.text(" escpos.image()\n")
p.text("================================\n")
p.text("\n")
# QR 이미지 생성
qr = qrcode.QRCode(version=1, box_size=3, border=2)
qr.add_data(TEST_URL)
qr.make(fit=True)
qr_img = qr.make_image(fill_color="black", back_color="white")
# escpos.image()로 인쇄
p.image(qr_img, center=True)
p.text("\n")
p.text(f"URL: {TEST_URL}\n")
p.text("\n\n\n")
p.cut()
print("✅ 방법 2 성공!")
time.sleep(2)
return True
except Exception as e:
print(f"❌ 방법 2 실패: {e}")
import traceback
traceback.print_exc()
return False
def test_method_3_qr_small():
"""방법 3: 작은 QR (size=3)"""
print("\n" + "="*60)
print("방법 3: 작은 QR (size=3)")
print("="*60)
try:
p = Network(PRINTER_IP, port=PRINTER_PORT)
p.text("\n")
p.text("================================\n")
p.text(" *** 방법 3 ***\n")
p.text(" 작은 QR (size=3)\n")
p.text("================================\n")
p.text("\n")
p.qr(TEST_URL, size=3, center=True)
p.text("\n")
p.text(f"URL: {TEST_URL}\n")
p.text("\n\n\n")
p.cut()
print("✅ 방법 3 성공!")
time.sleep(2)
return True
except Exception as e:
print(f"❌ 방법 3 실패: {e}")
import traceback
traceback.print_exc()
return False
def test_method_4_qr_large():
"""방법 4: 큰 QR (size=8)"""
print("\n" + "="*60)
print("방법 4: 큰 QR (size=8)")
print("="*60)
try:
p = Network(PRINTER_IP, port=PRINTER_PORT)
p.text("\n")
p.text("================================\n")
p.text(" *** 방법 4 ***\n")
p.text(" 큰 QR (size=8)\n")
p.text("================================\n")
p.text("\n")
p.qr(TEST_URL, size=8, center=True)
p.text("\n")
p.text(f"URL: {TEST_URL}\n")
p.text("\n\n\n")
p.cut()
print("✅ 방법 4 성공!")
time.sleep(2)
return True
except Exception as e:
print(f"❌ 방법 4 실패: {e}")
import traceback
traceback.print_exc()
return False
def test_method_5_full_receipt():
"""방법 5: 완전한 영수증 (청춘약국)"""
print("\n" + "="*60)
print("방법 5: 완전한 영수증")
print("="*60)
try:
p = Network(PRINTER_IP, port=PRINTER_PORT)
p.text("\n")
p.text("================================\n")
p.text(" *** 방법 5 ***\n")
p.text(" 완전한 영수증\n")
p.text("================================\n")
p.text("\n")
# 헤더
p.set(align='center')
p.text("청춘약국\n")
p.text("================================\n")
# 거래 정보
p.set(align='left')
p.text("거래일시: 2026-01-29 14:30\n")
p.text("거래번호: 20260129000042\n")
p.text("\n")
p.text("결제금액: 50,000원\n")
p.text("적립예정: 1,500P\n")
p.text("\n")
p.text("================================\n")
p.text("\n")
# QR 코드
p.qr(TEST_URL, size=6, center=True)
p.text("\n")
p.set(align='center')
p.text("QR 촬영하고 포인트 받으세요!\n")
p.text("\n")
p.text("================================\n")
p.text("\n\n\n")
p.cut()
print("✅ 방법 5 성공!")
time.sleep(2)
return True
except Exception as e:
print(f"❌ 방법 5 실패: {e}")
import traceback
traceback.print_exc()
return False
def main():
print("="*60)
print("python-escpos 라이브러리 QR 테스트")
print("="*60)
print(f"프린터: {PRINTER_IP}:{PRINTER_PORT}")
print(f"테스트 URL: {TEST_URL}")
print("\n먼저 라이브러리 설치 확인:")
print(" pip install python-escpos")
print("="*60)
try:
import escpos
print("✅ python-escpos 설치됨")
except ImportError:
print("❌ python-escpos가 설치되지 않았습니다!")
print(" 실행: pip install python-escpos")
return
methods = [
("방법 1: 내장 QR (size=4)", test_method_1_native_qr),
("방법 2: 이미지로 변환", test_method_2_image),
("방법 3: 작은 QR (size=3)", test_method_3_qr_small),
("방법 4: 큰 QR (size=8)", test_method_4_qr_large),
("방법 5: 완전한 영수증", test_method_5_full_receipt),
]
results = []
for name, method_func in methods:
try:
success = method_func()
results.append((name, success))
except Exception as e:
print(f"[{name}] ❌ 예외 발생: {e}")
results.append((name, False))
print("\n" + "="*60)
print("결과 요약")
print("="*60)
for name, success in results:
print(f"{name}: {'✅ 성공' if success else '❌ 실패'}")
print("\n5장의 영수증을 확인하여 QR이 보이는 번호를 알려주세요!")
print("="*60)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,122 @@
# -*- coding: utf-8 -*-
"""rx-usage 페이지 Playwright 테스트"""
from playwright.sync_api import sync_playwright
import time
import json
def test_rx_usage_quick_order():
with sync_playwright() as p:
browser = p.chromium.launch(headless=False) # 화면 보이게
page = browser.new_page()
# 콘솔 로그 캡처
page.on("console", lambda msg: print(f"[CONSOLE] {msg.type}: {msg.text}"))
# 네트워크 요청/응답 캡처
def log_response(response):
if 'api/order' in response.url or 'quick-submit' in response.url:
print(f"\n[RESPONSE] {response.url}")
print(f" Status: {response.status}")
try:
body = response.json()
print(f" Body: {json.dumps(body, ensure_ascii=False, indent=2)}")
except:
print(f" Body: {response.text()[:500]}")
page.on("response", log_response)
print("="*60)
print("1. rx-usage 페이지 접속")
print("="*60)
page.goto("http://localhost:7001/admin/rx-usage")
page.wait_for_load_state("networkidle")
time.sleep(2)
print("\n" + "="*60)
print("2. 데이터 로드 (조회 버튼 클릭)")
print("="*60)
# 조회 버튼 클릭
search_btn = page.locator("button:has-text('조회')")
if search_btn.count() > 0:
search_btn.first.click()
time.sleep(3)
print("\n" + "="*60)
print("3. 첫 번째 품목 행 더블클릭 (도매상 모달 열기)")
print("="*60)
# 테이블 행 찾기
rows = page.locator("tr[data-idx]")
row_count = rows.count()
print(f" 품목 수: {row_count}")
if row_count > 0:
# 첫 번째 품목 더블클릭
rows.first.dblclick()
time.sleep(3)
print("\n" + "="*60)
print("4. 도매상 모달에서 지오영 품목 확인")
print("="*60)
# 지오영 테이블에서 재고 있는 품목 찾기
geo_rows = page.locator(".geo-table tbody tr:not(.no-stock)")
geo_count = geo_rows.count()
print(f" 지오영 재고 있는 품목: {geo_count}")
if geo_count > 0:
# 첫 번째 품목 정보 출력
first_row = geo_rows.first
product_name = first_row.locator(".geo-name").text_content()
stock = first_row.locator(".geo-stock").text_content()
print(f" 선택할 품목: {product_name}, 재고: {stock}")
print("\n" + "="*60)
print("5. '담기' 버튼 클릭")
print("="*60)
add_btn = first_row.locator("button.geo-add-btn")
if add_btn.count() > 0:
add_btn.click()
time.sleep(1)
# prompt 창에 수량 입력 (기본값 사용)
page.on("dialog", lambda dialog: dialog.accept())
time.sleep(2)
print("\n" + "="*60)
print("6. 장바구니 확인")
print("="*60)
cart_items = page.locator(".cart-item")
cart_count = cart_items.count()
print(f" 장바구니 품목: {cart_count}")
if cart_count > 0:
print("\n" + "="*60)
print("7. 퀵주문 버튼 클릭!")
print("="*60)
# 퀵주문 버튼 찾기
quick_order_btn = page.locator("button.cart-item-order").first
if quick_order_btn.count() > 0:
quick_order_btn.click()
time.sleep(1)
# confirm 대화상자 수락
page.on("dialog", lambda dialog: dialog.accept())
time.sleep(5)
print("\n" + "="*60)
print("8. 결과 확인")
print("="*60)
# 토스트 메시지 확인
toast = page.locator(".toast")
if toast.count() > 0:
toast_text = toast.text_content()
print(f" 토스트 메시지: {toast_text}")
print("\n테스트 완료. 10초 후 브라우저 닫힘...")
time.sleep(10)
browser.close()
if __name__ == "__main__":
test_rx_usage_quick_order()

View File

@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
"""선별 주문 테스트 - 체크박스로 특정 품목만 주문"""
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import SooinSession
s = SooinSession()
s.login()
s.clear_cart()
# 1. 품목 2개 담기
print('=== 1. 품목 2개 담기 ===')
r1 = s.search_products('코자정')
p1 = r1['items'][0]
s.add_to_cart(p1['internal_code'], qty=1, price=p1['price'], stock=p1['stock'])
print(f"담음: {p1['name']}")
r2 = s.search_products('디카맥스')
p2 = r2['items'][0]
s.add_to_cart(p2['internal_code'], qty=1, price=p2['price'], stock=p2['stock'])
print(f"담음: {p2['name']}")
# 2. 장바구니 확인
print('\n=== 2. 장바구니 확인 ===')
cart = s.get_cart()
print(f"품목 수: {cart['total_items']}")
for item in cart['items']:
status = '활성' if item.get('active') else '취소'
print(f" [{status}] {item['product_name'][:25]} (row:{item['row_index']})")
# 3. 코자정(row 0)만 취소 → 디카맥스만 주문되어야 함
print('\n=== 3. 코자정 취소 (row 0) ===')
cancel_result = s.cancel_item(row_index=0)
print(f"취소 결과: {cancel_result}")
# 4. 장바구니 다시 확인
print('\n=== 4. 장바구니 재확인 ===')
cart2 = s.get_cart()
for item in cart2['items']:
status = '✅활성' if item.get('active') else '❌취소'
print(f" {status} {item['product_name'][:25]}")
# 5. 주문 (취소 안 된 것만 나감)
print('\n=== 5. 주문 전송 ===')
order_result = s.submit_order()
print(f"주문 결과: {order_result}")
# 6. 장바구니 확인 - 디카맥스만 주문됐으면, 코자정은 남아있어야 함
print('\n=== 6. 주문 후 장바구니 ===')
cart3 = s.get_cart()
print(f"품목 수: {cart3['total_items']}")
for item in cart3['items']:
print(f" - {item['product_name'][:25]}")
if cart3['total_items'] == 1:
print('\n🎉 성공! 취소된 품목(코자정)은 남고, 디카맥스만 주문됨!')
elif cart3['total_items'] == 0:
print('\n⚠️ 둘 다 주문됨 - 체크박스 로직 안 먹힘')
else:
print('\n🤔 예상 외 결과')

View File

@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
"""선별 주문 테스트 - 모듈 리로드 포함"""
import sys; sys.path.insert(0, '.'); import wholesale_path
# 모듈 리로드!
import importlib
import wholesale.sooin
importlib.reload(wholesale.sooin)
from wholesale import SooinSession
# 싱글톤 리셋
SooinSession._instance = None
s = SooinSession()
s.login()
s.clear_cart()
# 1. 품목 2개 담기
print('=== 1. 품목 2개 담기 ===')
r1 = s.search_products('코자정')
p1 = r1['items'][0]
s.add_to_cart(p1['internal_code'], qty=1, price=p1['price'], stock=p1['stock'])
print(f"담음: {p1['name']}")
r2 = s.search_products('디카맥스')
p2 = r2['items'][0]
s.add_to_cart(p2['internal_code'], qty=1, price=p2['price'], stock=p2['stock'])
print(f"담음: {p2['name']}")
# 2. 장바구니 확인
print('\n=== 2. 장바구니 확인 ===')
cart = s.get_cart()
print(f"품목 수: {cart['total_items']}")
for item in cart['items']:
status = '활성' if item.get('active') else '취소'
print(f" [{status}] {item['product_name'][:25]} (row:{item['row_index']})")
# 3. 첫 번째 품목(row 0) 취소 → 두 번째만 주문되어야 함
print('\n=== 3. 첫 번째 품목 취소 (row 0) ===')
cancel_result = s.cancel_item(row_index=0)
print(f"취소 결과: {cancel_result.get('message')}")
# 4. 장바구니 다시 확인
print('\n=== 4. 장바구니 재확인 ===')
cart2 = s.get_cart()
for item in cart2['items']:
status = '✅활성' if item.get('active') else '❌취소'
print(f" {status} {item['product_name'][:25]}")
# 5. 주문 (취소 안 된 것만 나감)
print('\n=== 5. 주문 전송 ===')
order_result = s.submit_order()
print(f"주문 결과: {order_result}")
# 6. 장바구니 확인
print('\n=== 6. 주문 후 장바구니 ===')
cart3 = s.get_cart()
print(f"품목 수: {cart3['total_items']}")
for item in cart3['items']:
print(f" - {item['product_name'][:25]}")
if cart3['total_items'] == 1:
print('\n🎉 성공! 취소된 품목은 남고, 나머지만 주문됨!')
elif cart3['total_items'] == 0:
print('\n⚠️ 둘 다 주문됨 - 체크박스 로직 안 먹힘')
else:
print(f'\n🤔 예상 외 결과: {cart3["total_items"]}개 남음')

38
backend/test_session.py Normal file
View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import SooinSession
from bs4 import BeautifulSoup
s = SooinSession()
print('1. 로그인...')
s.login()
print('\n2. 장바구니 비우기...')
s.clear_cart()
print('\n3. Bag.asp 확인 (비우기 후)...')
resp1 = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
soup1 = BeautifulSoup(resp1.content, 'html.parser')
int_array1 = soup1.find('input', {'name': 'intArray'})
print(f" intArray: {int_array1.get('value') if int_array1 else 'N/A'}")
print('\n4. 코자정 검색...')
result = s.search_products('코자정')
product = result['items'][0] if result.get('items') else None
print(f" 제품: {product['name']}, 코드: {product['internal_code']}")
print('\n5. add_to_cart 호출...')
cart_result = s.add_to_cart(product['internal_code'], qty=1,
price=product['price'], stock=product['stock'])
print(f" 결과: {cart_result}")
print('\n6. Bag.asp 확인 (담기 후)...')
resp2 = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
soup2 = BeautifulSoup(resp2.content, 'html.parser')
int_array2 = soup2.find('input', {'name': 'intArray'})
print(f" intArray: {int_array2.get('value') if int_array2 else 'N/A'}")
# 품목 확인
import re
rows = soup2.find_all('tr', id=re.compile(r'^bagLine'))
print(f" 품목 수: {len(rows)}")

View File

@@ -1,49 +0,0 @@
# -*- coding: utf-8 -*-
"""수인약품 API 테스트"""
import time
import sys
# 현재 디렉토리 추가
sys.path.insert(0, '.')
from sooin_api import SooinSession
print('수인약품 API 테스트')
print('='*50)
session = SooinSession()
# 1. 로그인 테스트
start = time.time()
print('1. 로그인 중...')
if session.login():
print(f' ✅ 로그인 성공! ({time.time()-start:.1f}초)')
else:
print(' ❌ 로그인 실패')
sys.exit(1)
# 2. 검색 테스트 (KD코드: 코자정)
start = time.time()
print('\n2. 검색 테스트 (KD코드: 073100220 - 코자정)...')
products = session.search_products('073100220', 'kd_code')
elapsed = time.time() - start
print(f' 검색 완료: {len(products)}개 ({elapsed:.2f}초)')
for p in products[:3]:
name = p.get('product_name', '')
spec = p.get('specification', '')
stock = p.get('stock', 0)
price = p.get('unit_price', 0)
code = p.get('internal_code', '')
print(f' - {name} ({spec})')
print(f' 재고: {stock}, 단가: {price:,}원, 내부코드: {code}')
# 3. 장바구니 조회
start = time.time()
print('\n3. 장바구니 조회...')
cart = session.get_cart()
elapsed = time.time() - start
print(f' 장바구니: {cart.get("total_items", 0)}개 품목 ({elapsed:.2f}초)')
print('\n' + '='*50)
print('✅ 테스트 완료!')

View File

@@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
"""수인약품 API 전체 플로우 테스트"""
import time
from sooin_api import SooinSession
session = SooinSession()
print('=== 수인약품 API 전체 테스트 ===')
print()
# 로그인
start = time.time()
session.login()
print(f'1. 로그인: {time.time()-start:.1f}')
# 장바구니 비우기
start = time.time()
session.clear_cart()
print(f'2. 장바구니 비우기: {time.time()-start:.2f}')
# 검색 + 장바구니 추가
start = time.time()
result = session.order_product('073100220', 2, '30T')
elapsed = time.time() - start
success = result.get('success', False)
msg = result.get('message', '')
print(f'3. 검색+장바구니: {elapsed:.2f}')
print(f' 결과: {success} - {msg}')
# 장바구니 조회
start = time.time()
cart = session.get_cart()
elapsed = time.time() - start
items = cart.get('total_items', 0)
amount = cart.get('total_amount', 0)
print(f'4. 장바구니 조회: {elapsed:.2f}')
print(f' 품목: {items}개, 금액: {amount:,}')
print()
print('=== 완료! ===')

View File

@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import SooinSession
from bs4 import BeautifulSoup
s = SooinSession()
print('1. 로그인 & 장바구니 담기...')
s.login()
s.clear_cart()
result = s.search_products('코자정')
product = result['items'][0]
s.add_to_cart(product['internal_code'], qty=1,
price=product['price'], stock=product['stock'])
# 장바구니 확인
resp = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
soup = BeautifulSoup(resp.content, 'html.parser')
int_array = soup.find('input', {'name': 'intArray'})
print(f" intArray: {int_array.get('value')}")
print('\n2. Form 데이터 수집...')
form = soup.find('form', {'id': 'frmBag'})
form_data = {}
for inp in form.find_all('input'):
name = inp.get('name', '')
if not name:
continue
inp_type = inp.get('type', 'text').lower()
if inp_type == 'checkbox':
continue
form_data[name] = inp.get('value', '')
# 주요 필드 출력
print(f" kind: {form_data.get('kind')}")
print(f" intArray: {form_data.get('intArray')}")
print(f" currVenCd: {form_data.get('currVenCd')}")
print('\n3. kind=order로 변경 후 POST...')
form_data['kind'] = 'order'
form_data['tx_memo'] = '디버그 테스트'
print(f" 전송할 필드 수: {len(form_data)}")
resp = s.session.post(
s.BAG_URL, # BagOrder.asp
data=form_data,
headers={
'Content-Type': 'application/x-www-form-urlencoded',
'Referer': f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}'
},
timeout=30
)
print(f'\n4. 응답 분석...')
print(f" 상태코드: {resp.status_code}")
print(f" 응답 길이: {len(resp.text)}")
print(f"\n 응답 내용:\n{resp.text[:1000]}")
print('\n5. 주문 후 장바구니 확인...')
resp2 = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
soup2 = BeautifulSoup(resp2.content, 'html.parser')
int_array2 = soup2.find('input', {'name': 'intArray'})
print(f" intArray: {int_array2.get('value')}")

View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
"""submit_order 메서드 테스트"""
import sys; sys.path.insert(0, '.'); import wholesale_path
# 모듈 리로드
import importlib
import wholesale.sooin
importlib.reload(wholesale.sooin)
from wholesale import SooinSession
s = SooinSession()
print('1. 로그인...')
s.login()
print('2. 장바구니 비우기...')
s.clear_cart()
print('3. 제품 검색 및 추가...')
result = s.search_products('코자정')
product = result['items'][0]
print(f" 제품: {product['name']} / {product['price']:,}")
s.add_to_cart(product['internal_code'], qty=1,
price=product['price'], stock=product['stock'])
print('4. 장바구니 확인...')
cart = s.get_cart()
print(f" 품목 수: {cart['total_items']}")
print(f" 총액: {cart['total_amount']:,}")
print('\n5. 주문 전송...')
order_result = s.submit_order(memo="API 테스트")
print(f" 결과: {order_result}")
if order_result.get('success'):
print('\n🎉 주문 성공!')
else:
print(f"\n❌ 주문 실패: {order_result.get('error')}")
print('\n6. 주문 후 장바구니...')
cart2 = s.get_cart()
print(f" 품목 수: {cart2['total_items']}")

66
backend/test_submit_xy.py Normal file
View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
import sys; sys.path.insert(0, '.'); import wholesale_path
from wholesale import SooinSession
from bs4 import BeautifulSoup
s = SooinSession()
print('1. 준비...')
s.login()
s.clear_cart()
result = s.search_products('코자정')
product = result['items'][0]
s.add_to_cart(product['internal_code'], qty=1,
price=product['price'], stock=product['stock'])
resp = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
soup = BeautifulSoup(resp.content, 'html.parser')
form = soup.find('form', {'id': 'frmBag'})
form_data = {}
for inp in form.find_all('input'):
name = inp.get('name', '')
if not name:
continue
inp_type = inp.get('type', 'text').lower()
if inp_type == 'checkbox':
continue
form_data[name] = inp.get('value', '')
form_data['kind'] = 'order'
form_data['tx_memo'] = '좌표 테스트'
# x, y 좌표 추가!
form_data['x'] = '10'
form_data['y'] = '10'
print(f" intArray: {form_data.get('intArray')}")
print(f" x, y 추가: {form_data.get('x')}, {form_data.get('y')}")
print('\n2. POST...')
resp = s.session.post(
s.BAG_URL,
data=form_data,
headers={
'Content-Type': 'application/x-www-form-urlencoded',
'Referer': f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}'
},
timeout=30
)
print(f" 응답 길이: {len(resp.text)}")
# alert 내용 확인
import re
alert_match = re.search(r'alert\("([^"]*)"\)', resp.text)
alert_msg = alert_match.group(1) if alert_match else 'N/A'
print(f" alert 메시지: '{alert_msg}'")
print('\n3. 주문 후 장바구니...')
resp2 = s.session.get(f'{s.BAG_VIEW_URL}?currVenCd={s.vendor_code}', timeout=15)
soup2 = BeautifulSoup(resp2.content, 'html.parser')
int_array2 = soup2.find('input', {'name': 'intArray'})
print(f" intArray: {int_array2.get('value')}")
if int_array2.get('value') == '-1':
print('\n🎉 주문 성공!')

81
backend/test_temp_save.py Normal file
View File

@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
"""방안 1: 임시 보관 방식 테스트"""
import sys; sys.path.insert(0, '.'); import wholesale_path
import importlib
import wholesale.sooin
importlib.reload(wholesale.sooin)
from wholesale import SooinSession
SooinSession._instance = None
s = SooinSession()
s.login()
s.clear_cart()
# 시나리오: 기존 코자정이 담겨있고, 디카맥스만 주문하고 싶음
print('=== 1. 기존 품목 (코자정) 담기 ===')
r1 = s.search_products('코자정')
p1 = r1['items'][0]
s.add_to_cart(p1['internal_code'], qty=1, price=p1['price'], stock=p1['stock'])
print(f"기존 품목: {p1['name']}")
print('\n=== 2. 새 품목 (디카맥스) 담기 ===')
r2 = s.search_products('디카맥스')
p2 = r2['items'][0]
s.add_to_cart(p2['internal_code'], qty=1, price=p2['price'], stock=p2['stock'])
print(f"새 품목: {p2['name']}")
# 장바구니 확인
cart = s.get_cart()
print(f"\n현재 장바구니: {cart['total_items']}")
# === 선별 주문 시작 ===
print('\n' + '='*50)
print('=== 선별 주문: 디카맥스만 주문 ===')
print('='*50)
# 3. 기존 품목 정보 저장
print('\n3. 기존 품목 정보 저장')
existing_items = []
for item in cart['items']:
# 디카맥스는 제외 (이번에 주문할 품목)
if '디카맥스' not in item['product_name']:
existing_items.append({
'internal_code': item['internal_code'],
'quantity': item['quantity'],
'price': item['unit_price'],
'name': item['product_name']
})
print(f" 저장: {item['product_name']}")
# 4. 장바구니 비우기
print('\n4. 장바구니 비우기')
s.clear_cart()
# 5. 주문할 품목만 다시 담기
print('\n5. 디카맥스만 다시 담기')
s.add_to_cart(p2['internal_code'], qty=1, price=p2['price'], stock=p2['stock'])
# 6. 주문
print('\n6. 주문!')
result = s.submit_order()
print(f"결과: {result}")
# 7. 기존 품목 복원
print('\n7. 기존 품목 복원')
for item in existing_items:
s.add_to_cart(item['internal_code'], qty=item['quantity'], price=item['price'], stock=999)
print(f" 복원: {item['name']}")
# 8. 최종 확인
print('\n=== 8. 최종 장바구니 ===')
final_cart = s.get_cart()
print(f"품목 수: {final_cart['total_items']}")
for item in final_cart['items']:
print(f" - {item['product_name']}")
if final_cart['total_items'] == 1 and '코자정' in final_cart['items'][0]['product_name']:
print('\n🎉 성공! 디카맥스만 주문되고 코자정은 복원됨!')
else:
print('\n❌ 실패')

View File

@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
"""방안 1: 재고 있는 품목으로 테스트"""
import sys; sys.path.insert(0, '.'); import wholesale_path
import importlib
import wholesale.sooin
importlib.reload(wholesale.sooin)
from wholesale import SooinSession
SooinSession._instance = None
s = SooinSession()
s.login()
s.clear_cart()
# 재고 있는 품목 검색
print('=== 1. 재고 확인 ===')
r1 = s.search_products('코자정')
r2 = s.search_products('라식스')
p1 = r1['items'][0]
p2 = r2['items'][0]
print(f"코자정: 재고 {p1['stock']}")
print(f"라식스: 재고 {p2['stock']}")
# 기존 품목 담기 (코자정 - 나중에 복원할 것)
print('\n=== 2. 기존 품목 (코자정) 담기 ===')
s.add_to_cart(p1['internal_code'], qty=1, price=p1['price'], stock=p1['stock'])
# 새 품목 담기 (라식스 - 주문할 것)
print('=== 3. 새 품목 (라식스) 담기 ===')
s.add_to_cart(p2['internal_code'], qty=1, price=p2['price'], stock=p2['stock'])
cart = s.get_cart()
print(f"현재 장바구니: {cart['total_items']}")
for item in cart['items']:
print(f" - {item['product_name'][:30]}")
# === 선별 주문 ===
print('\n' + '='*50)
print('=== 라식스만 주문! ===')
print('='*50)
# 기존 품목 저장
existing = [{'ic': p1['internal_code'], 'qty': 1, 'price': p1['price'], 'stock': p1['stock'], 'name': p1['name']}]
print(f"\n저장: {p1['name']}")
# 장바구니 비우기
print('장바구니 비우기...')
s.clear_cart()
# 라식스만 담기
print('라식스만 담기...')
s.add_to_cart(p2['internal_code'], qty=1, price=p2['price'], stock=p2['stock'])
# 주문
print('주문 전송...')
result = s.submit_order()
print(f"결과: {result}")
# 복원
print('\n코자정 복원...')
for e in existing:
s.add_to_cart(e['ic'], qty=e['qty'], price=e['price'], stock=e['stock'])
# 최종 확인
final = s.get_cart()
print(f"\n=== 최종 장바구니: {final['total_items']}개 ===")
for item in final['items']:
print(f" - {item['product_name'][:30]}")
if final['total_items'] == 1:
print('\n🎉 성공! 라식스만 주문됨, 코자정 복원됨!')

View File

@@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
"""wholesale 통합 테스트"""
import wholesale_path
from wholesale import SooinSession, GeoYoungSession
print('=== 도매상 API 통합 테스트 ===\n')
# 수인약품 테스트
print('1. 수인약품 테스트')
sooin = SooinSession()
if sooin.login():
print(' ✅ 로그인 성공')
result = sooin.search_products('073100220')
print(f' ✅ 검색: {result["total"]}개 결과')
cart = sooin.get_cart()
print(f' ✅ 장바구니: {cart["total_items"]}')
else:
print(' ❌ 로그인 실패')
# 지오영 테스트
print('\n2. 지오영 테스트')
geo = GeoYoungSession()
if geo.login():
print(' ✅ 로그인 성공')
result = geo.search_products('레바미피드')
print(f' ✅ 검색: {result["total"]}개 결과')
cart = geo.get_cart()
print(f' ✅ 장바구니: {cart["total_items"]}')
else:
print(' ❌ 로그인 실패')
print('\n=== 테스트 완료 ===')