## 구현 내용 ### 1. 백엔드 (app.py) - 수동 로트 배분 지원 (lot_assignments 배열 처리) - 각 로트별 지정 수량만큼 재고 차감 - 검증: 배분 합계 확인, 재고 충분 확인 - compound_consumptions 테이블에 각 로트별 소비 기록 ### 2. 프론트엔드 (app.js, index.html) - 로트 배분 모달 UI 구현 - 로트별 재고, 단가 표시 - 수동 입력 및 자동 배분 기능 - 실시간 합계 계산 및 검증 - 원산지 선택에 "수동 배분" 옵션 추가 (로트 2개 이상 시) - 조제 저장 시 lot_assignments 포함 ### 3. 테스트 - 테스트용 당귀 로트 추가 (한국산) - E2E 테스트 성공 - 당귀 100g을 2개 로트(중국산 60g + 한국산 40g)로 배분 - 각 로트별 재고 정확히 차감 - 소비 내역 올바르게 기록 ## 장점 - DB 스키마 변경 없음 - 기존 자동 선택과 호환 - 재고 부족 시 여러 로트 조합 가능 - 원가 최적화 가능 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
112 lines
4.3 KiB
Python
112 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
복합 로트 사용 E2E 테스트
|
|
- 당귀 2개 로트를 수동 배분하여 커스텀 조제 테스트
|
|
"""
|
|
|
|
import json
|
|
import requests
|
|
from datetime import datetime
|
|
|
|
BASE_URL = "http://localhost:5001"
|
|
|
|
def test_multi_lot_compound():
|
|
print("=== 복합 로트 사용 E2E 테스트 시작 ===\n")
|
|
|
|
# 1. 당귀 재고 현황 확인
|
|
print("1. 당귀(휴먼일당귀) 재고 현황 확인")
|
|
response = requests.get(f"{BASE_URL}/api/herbs/63/available-lots")
|
|
if response.status_code == 200:
|
|
data = response.json()['data']
|
|
print(f" - 약재명: {data['herb_name']}")
|
|
print(f" - 총 재고: {data['total_quantity']}g")
|
|
|
|
for origin in data['origins']:
|
|
print(f"\n [{origin['origin_country']}] 로트 {origin['lot_count']}개, 총 {origin['total_quantity']}g")
|
|
for lot in origin['lots']:
|
|
print(f" - 로트 #{lot['lot_id']}: {lot['quantity_onhand']}g @ {lot['unit_price_per_g']}원/g")
|
|
else:
|
|
print(f" ❌ 오류: {response.status_code}")
|
|
return
|
|
|
|
# 2. 커스텀 조제 생성 (당귀 100g 필요)
|
|
print("\n2. 커스텀 조제 생성 - 당귀 100g를 2개 로트로 수동 배분")
|
|
|
|
compound_data = {
|
|
"patient_id": 1, # 테스트 환자
|
|
"formula_id": None, # 커스텀 조제
|
|
"je_count": 1,
|
|
"cheop_total": 1,
|
|
"pouch_total": 1,
|
|
"ingredients": [
|
|
{
|
|
"herb_item_id": 63, # 휴먼일당귀
|
|
"grams_per_cheop": 100.0,
|
|
"total_grams": 100.0, # total_grams 추가
|
|
"origin": "manual", # 수동 배분
|
|
"lot_assignments": [
|
|
{"lot_id": 208, "quantity": 60.0}, # 중국산 60g
|
|
{"lot_id": 219, "quantity": 40.0} # 한국산 40g
|
|
]
|
|
}
|
|
]
|
|
}
|
|
|
|
print(" - 로트 배분:")
|
|
print(" * 로트 #208 (중국산): 60g")
|
|
print(" * 로트 #219 (한국산): 40g")
|
|
|
|
response = requests.post(
|
|
f"{BASE_URL}/api/compounds",
|
|
json=compound_data,
|
|
headers={"Content-Type": "application/json"}
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
result = response.json()
|
|
if result.get('success'):
|
|
compound_id = result.get('compound_id')
|
|
total_cost = result.get('total_cost')
|
|
print(f"\n ✅ 조제 성공!")
|
|
print(f" - 조제 ID: {compound_id}")
|
|
print(f" - 총 원가: {total_cost}원")
|
|
|
|
# 3. 조제 상세 확인
|
|
print("\n3. 조제 상세 정보 확인")
|
|
response = requests.get(f"{BASE_URL}/api/compounds/{compound_id}")
|
|
if response.status_code == 200:
|
|
detail = response.json()['data']
|
|
|
|
print(" - 소비 내역:")
|
|
for con in detail.get('consumptions', []):
|
|
print(f" * 로트 #{con['lot_id']}: {con['quantity_used']}g @ {con['unit_cost_per_g']}원/g = {con['cost_amount']}원")
|
|
|
|
# 4. 재고 변동 확인
|
|
print("\n4. 재고 변동 확인")
|
|
response = requests.get(f"{BASE_URL}/api/herbs/63/available-lots")
|
|
if response.status_code == 200:
|
|
after_data = response.json()['data']
|
|
print(" - 조제 후 재고:")
|
|
for origin in after_data['origins']:
|
|
for lot in origin['lots']:
|
|
if lot['lot_id'] in [208, 219]:
|
|
print(f" * 로트 #{lot['lot_id']} ({origin['origin_country']}): {lot['quantity_onhand']}g")
|
|
|
|
print("\n✅ 복합 로트 사용 테스트 성공!")
|
|
print(" - 2개의 로트를 수동으로 배분하여 조제")
|
|
print(" - 각 로트별 재고가 정확히 차감됨")
|
|
print(" - 소비 내역이 올바르게 기록됨")
|
|
|
|
else:
|
|
print(f" ❌ 상세 조회 실패: {response.status_code}")
|
|
else:
|
|
print(f" ❌ 조제 실패: {result.get('error')}")
|
|
else:
|
|
print(f" ❌ API 호출 실패: {response.status_code}")
|
|
print(f" 응답: {response.text}")
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
test_multi_lot_compound()
|
|
except Exception as e:
|
|
print(f"\n❌ 테스트 중 오류 발생: {e}") |