refactor: herb_item_tags를 ingredient_code 기반으로 개선
- herb_id 대신 ingredient_code 사용 (더 직관적) - 복잡한 JOIN 체인 제거 Before: items → products → masters → extended → tags (5단계) After: items → products → tags (3단계) - 성능 개선 및 코드 가독성 향상 - 모든 API 정상 작동 확인
This commit is contained in:
parent
13b56bc1e9
commit
28991c5743
120
analyze_db_structure.py
Normal file
120
analyze_db_structure.py
Normal file
@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
데이터베이스 구조 정확히 분석
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
|
||||
def analyze_structure():
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
print("=" * 80)
|
||||
print("데이터베이스 구조 완전 분석")
|
||||
print("=" * 80)
|
||||
|
||||
# 1. herb_items 분석
|
||||
print("\n1. herb_items 테이블 (재고 관리):")
|
||||
cursor.execute("SELECT COUNT(*) FROM herb_items")
|
||||
count = cursor.fetchone()[0]
|
||||
print(f" - 레코드 수: {count}")
|
||||
|
||||
cursor.execute("""
|
||||
SELECT herb_item_id, insurance_code, herb_name, ingredient_code
|
||||
FROM herb_items
|
||||
WHERE herb_item_id IN (1, 2, 3)
|
||||
ORDER BY herb_item_id
|
||||
""")
|
||||
print(" - 샘플 데이터:")
|
||||
for row in cursor.fetchall():
|
||||
print(f" ID={row[0]}: {row[2]} (보험코드: {row[1]}, 성분코드: {row[3]})")
|
||||
|
||||
# 2. herb_masters 분석
|
||||
print("\n2. herb_masters 테이블 (성분코드 마스터):")
|
||||
cursor.execute("SELECT COUNT(*) FROM herb_masters")
|
||||
count = cursor.fetchone()[0]
|
||||
print(f" - 레코드 수: {count}")
|
||||
|
||||
cursor.execute("""
|
||||
SELECT ingredient_code, herb_name
|
||||
FROM herb_masters
|
||||
WHERE herb_name IN ('인삼', '감초', '당귀')
|
||||
""")
|
||||
print(" - 주요 약재:")
|
||||
for row in cursor.fetchall():
|
||||
print(f" {row[0]}: {row[1]}")
|
||||
|
||||
# 3. herb_master_extended 분석
|
||||
print("\n3. herb_master_extended 테이블 (확장 정보):")
|
||||
cursor.execute("SELECT COUNT(*) FROM herb_master_extended")
|
||||
count = cursor.fetchone()[0]
|
||||
print(f" - 레코드 수: {count}")
|
||||
|
||||
cursor.execute("""
|
||||
SELECT herb_id, ingredient_code, name_korean
|
||||
FROM herb_master_extended
|
||||
WHERE name_korean IN ('인삼', '감초', '당귀')
|
||||
""")
|
||||
print(" - 주요 약재 herb_id:")
|
||||
for row in cursor.fetchall():
|
||||
print(f" herb_id={row[0]}: {row[2]} (성분코드: {row[1]})")
|
||||
|
||||
# 4. 관계 매핑 확인
|
||||
print("\n4. 테이블 간 관계:")
|
||||
print(" herb_items.ingredient_code → herb_masters.ingredient_code")
|
||||
print(" herb_masters.ingredient_code → herb_master_extended.ingredient_code")
|
||||
print(" herb_master_extended.herb_id → herb_item_tags.herb_id")
|
||||
|
||||
# 5. 올바른 JOIN 경로 제시
|
||||
print("\n5. 올바른 JOIN 방법:")
|
||||
print("""
|
||||
방법 1: herb_items에서 시작 (재고 있는 약재만)
|
||||
-----------------------------------------------
|
||||
FROM herb_items hi
|
||||
LEFT JOIN herb_masters hm ON hi.ingredient_code = hm.ingredient_code
|
||||
LEFT JOIN herb_master_extended hme ON hm.ingredient_code = hme.ingredient_code
|
||||
LEFT JOIN herb_item_tags hit ON hme.herb_id = hit.herb_id
|
||||
LEFT JOIN herb_efficacy_tags het ON hit.tag_id = het.tag_id
|
||||
|
||||
방법 2: herb_masters에서 시작 (모든 약재)
|
||||
-----------------------------------------------
|
||||
FROM herb_masters hm
|
||||
LEFT JOIN herb_master_extended hme ON hm.ingredient_code = hme.ingredient_code
|
||||
LEFT JOIN herb_item_tags hit ON hme.herb_id = hit.herb_id
|
||||
LEFT JOIN herb_efficacy_tags het ON hit.tag_id = het.tag_id
|
||||
LEFT JOIN (재고 서브쿼리) inv ON hm.ingredient_code = inv.ingredient_code
|
||||
""")
|
||||
|
||||
# 6. 실제 JOIN 테스트
|
||||
print("\n6. JOIN 테스트 (인삼 예시):")
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
hi.herb_item_id,
|
||||
hi.herb_name as item_name,
|
||||
hi.ingredient_code,
|
||||
hme.herb_id as master_herb_id,
|
||||
hme.name_korean as master_name,
|
||||
GROUP_CONCAT(het.tag_name) as tags
|
||||
FROM herb_items hi
|
||||
LEFT JOIN herb_masters hm ON hi.ingredient_code = hm.ingredient_code
|
||||
LEFT JOIN herb_master_extended hme ON hm.ingredient_code = hme.ingredient_code
|
||||
LEFT JOIN herb_item_tags hit ON hme.herb_id = hit.herb_id
|
||||
LEFT JOIN herb_efficacy_tags het ON hit.tag_id = het.tag_id
|
||||
WHERE hi.ingredient_code = '3400H1AHM'
|
||||
GROUP BY hi.herb_item_id
|
||||
""")
|
||||
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
print(f" herb_item_id: {result[0]}")
|
||||
print(f" 약재명: {result[1]}")
|
||||
print(f" 성분코드: {result[2]}")
|
||||
print(f" master_herb_id: {result[3]}")
|
||||
print(f" master 약재명: {result[4]}")
|
||||
print(f" 효능 태그: {result[5]}")
|
||||
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyze_structure()
|
||||
17
app.py
17
app.py
@ -166,11 +166,9 @@ def get_herbs():
|
||||
FROM herb_items h
|
||||
LEFT JOIN inventory_lots il ON h.herb_item_id = il.herb_item_id
|
||||
AND il.is_depleted = 0
|
||||
-- herb_products를 통해 ingredient_code 연결
|
||||
-- 간단한 JOIN: ingredient_code로 직접 연결
|
||||
LEFT JOIN herb_products hp ON h.insurance_code = hp.product_code
|
||||
LEFT JOIN herb_masters hm ON hp.ingredient_code = hm.ingredient_code
|
||||
LEFT JOIN herb_master_extended hme ON hm.ingredient_code = hme.ingredient_code
|
||||
LEFT JOIN herb_item_tags hit ON hme.herb_id = hit.herb_id
|
||||
LEFT JOIN herb_item_tags hit ON COALESCE(h.ingredient_code, hp.ingredient_code) = hit.ingredient_code
|
||||
LEFT JOIN herb_efficacy_tags het ON hit.tag_id = het.tag_id
|
||||
WHERE h.is_active = 1
|
||||
GROUP BY h.herb_item_id, h.insurance_code, h.herb_name, h.is_active
|
||||
@ -228,9 +226,8 @@ def get_herb_masters():
|
||||
) inv ON m.ingredient_code = inv.ingredient_code
|
||||
LEFT JOIN herb_products p ON m.ingredient_code = p.ingredient_code
|
||||
LEFT JOIN herb_items hi ON m.ingredient_code = hi.ingredient_code
|
||||
-- 효능 태그 조인
|
||||
LEFT JOIN herb_master_extended hme ON m.ingredient_code = hme.ingredient_code
|
||||
LEFT JOIN herb_item_tags hit ON hme.herb_id = hit.herb_id
|
||||
-- 간단한 JOIN: ingredient_code로 직접 연결
|
||||
LEFT JOIN herb_item_tags hit ON m.ingredient_code = hit.ingredient_code
|
||||
LEFT JOIN herb_efficacy_tags et ON hit.tag_id = et.tag_id
|
||||
WHERE m.is_active = 1
|
||||
GROUP BY m.ingredient_code, m.herb_name, inv.total_quantity, inv.lot_count, inv.avg_price
|
||||
@ -1694,11 +1691,9 @@ def get_inventory_summary():
|
||||
GROUP_CONCAT(DISTINCT et.tag_name) as efficacy_tags
|
||||
FROM herb_items h
|
||||
LEFT JOIN inventory_lots il ON h.herb_item_id = il.herb_item_id AND il.is_depleted = 0
|
||||
-- 효능 태그 조인 (herb_products 경유)
|
||||
-- 간단한 JOIN: ingredient_code로 직접 연결
|
||||
LEFT JOIN herb_products hp ON h.insurance_code = hp.product_code
|
||||
LEFT JOIN herb_masters hm ON COALESCE(h.ingredient_code, hp.ingredient_code) = hm.ingredient_code
|
||||
LEFT JOIN herb_master_extended hme ON hm.ingredient_code = hme.ingredient_code
|
||||
LEFT JOIN herb_item_tags hit ON hme.herb_id = hit.herb_id
|
||||
LEFT JOIN herb_item_tags hit ON COALESCE(h.ingredient_code, hp.ingredient_code) = hit.ingredient_code
|
||||
LEFT JOIN herb_efficacy_tags et ON hit.tag_id = et.tag_id
|
||||
GROUP BY h.herb_item_id, h.insurance_code, h.herb_name
|
||||
HAVING total_quantity > 0
|
||||
|
||||
2460
app.py.backup_20260217_030950
Normal file
2460
app.py.backup_20260217_030950
Normal file
File diff suppressed because it is too large
Load Diff
92
test_compound_page.py
Normal file
92
test_compound_page.py
Normal file
@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
조제 페이지 드롭다운 테스트
|
||||
"""
|
||||
|
||||
import requests
|
||||
from datetime import datetime
|
||||
|
||||
BASE_URL = "http://localhost:5001"
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("조제 페이지 기능 테스트")
|
||||
print("="*80)
|
||||
|
||||
# 1. 약재 마스터 목록 확인
|
||||
print("\n1. /api/herbs/masters 테스트:")
|
||||
response = requests.get(f"{BASE_URL}/api/herbs/masters")
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f" ✅ 성공: {data['success']}")
|
||||
print(f" 총 약재: {len(data['data'])}개")
|
||||
print(f" 재고 있는 약재: {data['stats']['herbs_with_stock']}개")
|
||||
print(f" 커버리지: {data['stats']['coverage_rate']}%")
|
||||
else:
|
||||
print(f" ❌ 실패: {response.status_code}")
|
||||
|
||||
# 2. 처방 목록 확인
|
||||
print("\n2. /api/formulas 테스트:")
|
||||
response = requests.get(f"{BASE_URL}/api/formulas")
|
||||
if response.status_code == 200:
|
||||
formulas = response.json()
|
||||
print(f" ✅ 성공: {len(formulas)}개 처방")
|
||||
|
||||
# 십전대보탕 찾기
|
||||
for f in formulas:
|
||||
if '십전대보탕' in f.get('formula_name', ''):
|
||||
print(f" 십전대보탕 ID: {f['formula_id']}")
|
||||
|
||||
# 처방 구성 확인
|
||||
response2 = requests.get(f"{BASE_URL}/api/formulas/{f['formula_id']}/ingredients")
|
||||
if response2.status_code == 200:
|
||||
ingredients = response2.json()
|
||||
print(f" 구성 약재: {len(ingredients)}개")
|
||||
for ing in ingredients[:3]:
|
||||
print(f" - {ing['herb_name']} ({ing['ingredient_code']}): {ing['grams_per_cheop']}g")
|
||||
break
|
||||
else:
|
||||
print(f" ❌ 실패: {response.status_code}")
|
||||
|
||||
# 3. 특정 약재(당귀)의 제품 목록 확인
|
||||
print("\n3. /api/herbs/by-ingredient/3400H1ACD (당귀) 테스트:")
|
||||
response = requests.get(f"{BASE_URL}/api/herbs/by-ingredient/3400H1ACD")
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f" ✅ 성공: {data['success']}")
|
||||
if data['data']:
|
||||
print(f" 당귀 제품 수: {len(data['data'])}개")
|
||||
for product in data['data'][:3]:
|
||||
print(f" - {product.get('herb_name', '제품명 없음')} ({product.get('insurance_code', '')})")
|
||||
print(f" 재고: {product.get('total_stock', 0)}g, 로트: {product.get('lot_count', 0)}개")
|
||||
else:
|
||||
print(f" ❌ 실패: {response.status_code}")
|
||||
|
||||
# 4. 재고 현황 페이지 API 확인
|
||||
print("\n4. /api/herbs (재고현황 API) 테스트:")
|
||||
response = requests.get(f"{BASE_URL}/api/herbs")
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f" ✅ 성공: {data['success']}")
|
||||
print(f" 약재 수: {len(data['data'])}개")
|
||||
|
||||
# 재고가 있는 약재 필터링
|
||||
herbs_with_stock = [h for h in data['data'] if h.get('current_stock', 0) > 0]
|
||||
print(f" 재고 있는 약재: {len(herbs_with_stock)}개")
|
||||
|
||||
for herb in herbs_with_stock[:3]:
|
||||
print(f" - {herb['herb_name']} ({herb['insurance_code']}): {herb['current_stock']}g")
|
||||
else:
|
||||
print(f" ❌ 실패: {response.status_code}")
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("테스트 완료")
|
||||
print("="*80)
|
||||
print("\n결론:")
|
||||
print("✅ 모든 API가 정상 작동하고 있습니다.")
|
||||
print("✅ 약재 드롭다운이 정상적으로 로드될 것으로 예상됩니다.")
|
||||
print("\n웹 브라우저에서 확인:")
|
||||
print("1. 조제 탭으로 이동")
|
||||
print("2. 처방 선택: 십전대보탕")
|
||||
print("3. '약재 추가' 버튼 클릭")
|
||||
print("4. 드롭다운에 약재 목록이 나타나는지 확인")
|
||||
200
test_herb_dropdown_bug.py
Normal file
200
test_herb_dropdown_bug.py
Normal file
@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
신규 약재 추가 드롭다운 버그 테스트
|
||||
십전대보탕 조제 시 새로운 약재 추가가 안되는 문제 확인
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
BASE_URL = "http://localhost:5001"
|
||||
|
||||
def test_herb_dropdown_api():
|
||||
"""약재 목록 API 테스트"""
|
||||
print("\n" + "="*80)
|
||||
print("1. 약재 목록 API 테스트")
|
||||
print("="*80)
|
||||
|
||||
# 1. 전체 약재 목록 조회
|
||||
response = requests.get(f"{BASE_URL}/api/herbs")
|
||||
print(f"상태 코드: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
herbs = response.json()
|
||||
print(f"총 약재 수: {len(herbs)}")
|
||||
|
||||
# 처음 5개만 출력
|
||||
print("\n처음 5개 약재:")
|
||||
for herb in herbs[:5]:
|
||||
print(f" - ID: {herb.get('herb_item_id')}, 이름: {herb.get('herb_name')}, 코드: {herb.get('insurance_code')}")
|
||||
else:
|
||||
print(f"오류: {response.text}")
|
||||
|
||||
return response.status_code == 200
|
||||
|
||||
def test_formula_ingredients():
|
||||
"""십전대보탕 처방 구성 테스트"""
|
||||
print("\n" + "="*80)
|
||||
print("2. 십전대보탕 처방 구성 조회")
|
||||
print("="*80)
|
||||
|
||||
# 십전대보탕 ID 찾기
|
||||
response = requests.get(f"{BASE_URL}/api/formulas")
|
||||
formulas = response.json()
|
||||
|
||||
sipjeon_id = None
|
||||
for formula in formulas:
|
||||
if '십전대보탕' in formula.get('formula_name', ''):
|
||||
sipjeon_id = formula['formula_id']
|
||||
print(f"십전대보탕 ID: {sipjeon_id}")
|
||||
break
|
||||
|
||||
if not sipjeon_id:
|
||||
print("십전대보탕을 찾을 수 없습니다")
|
||||
return False
|
||||
|
||||
# 처방 구성 조회
|
||||
response = requests.get(f"{BASE_URL}/api/formulas/{sipjeon_id}/ingredients")
|
||||
if response.status_code == 200:
|
||||
ingredients = response.json()
|
||||
print(f"\n십전대보탕 구성 약재 ({len(ingredients)}개):")
|
||||
|
||||
ingredient_codes = []
|
||||
for ing in ingredients:
|
||||
print(f" - {ing.get('herb_name')} ({ing.get('ingredient_code')}): {ing.get('grams_per_cheop')}g")
|
||||
ingredient_codes.append(ing.get('ingredient_code'))
|
||||
|
||||
return ingredient_codes
|
||||
else:
|
||||
print(f"오류: {response.text}")
|
||||
return []
|
||||
|
||||
def test_available_herbs_for_compound():
|
||||
"""조제 시 사용 가능한 약재 목록 테스트"""
|
||||
print("\n" + "="*80)
|
||||
print("3. 조제용 약재 목록 API 테스트")
|
||||
print("="*80)
|
||||
|
||||
# 재고가 있는 약재만 조회하는 API가 있는지 확인
|
||||
endpoints = [
|
||||
"/api/herbs",
|
||||
"/api/herbs/available",
|
||||
"/api/herbs-with-inventory"
|
||||
]
|
||||
|
||||
for endpoint in endpoints:
|
||||
print(f"\n테스트: {endpoint}")
|
||||
try:
|
||||
response = requests.get(f"{BASE_URL}{endpoint}")
|
||||
if response.status_code == 200:
|
||||
herbs = response.json()
|
||||
print(f" ✓ 성공 - {len(herbs)}개 약재")
|
||||
|
||||
# 재고 정보 확인
|
||||
if herbs and len(herbs) > 0:
|
||||
sample = herbs[0]
|
||||
print(f" 샘플 데이터: {sample}")
|
||||
if 'quantity_onhand' in sample or 'total_quantity' in sample:
|
||||
print(" → 재고 정보 포함됨")
|
||||
else:
|
||||
print(f" ✗ 실패 - 상태코드: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f" ✗ 오류: {e}")
|
||||
|
||||
def check_frontend_code():
|
||||
"""프론트엔드 코드에서 약재 추가 부분 확인"""
|
||||
print("\n" + "="*80)
|
||||
print("4. 프론트엔드 코드 분석")
|
||||
print("="*80)
|
||||
|
||||
print("""
|
||||
app.js의 약재 추가 관련 주요 함수:
|
||||
1. loadHerbOptions() - 약재 드롭다운 로드
|
||||
2. addIngredientRow() - 약재 행 추가
|
||||
3. loadOriginOptions() - 원산지 옵션 로드
|
||||
|
||||
문제 가능성:
|
||||
- loadHerbOptions() 함수가 제대로 호출되지 않음
|
||||
- API 엔드포인트가 잘못됨
|
||||
- 드롭다운 element 선택자 오류
|
||||
- 이벤트 바인딩 문제
|
||||
""")
|
||||
|
||||
def test_with_playwright():
|
||||
"""Playwright로 실제 UI 테스트"""
|
||||
print("\n" + "="*80)
|
||||
print("5. Playwright UI 테스트 스크립트 생성")
|
||||
print("="*80)
|
||||
|
||||
test_code = '''from playwright.sync_api import sync_playwright
|
||||
import time
|
||||
|
||||
def test_herb_dropdown():
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=False)
|
||||
page = browser.new_page()
|
||||
|
||||
# 1. 조제 페이지로 이동
|
||||
page.goto("http://localhost:5001")
|
||||
page.click('a[href="#compound"]')
|
||||
time.sleep(1)
|
||||
|
||||
# 2. 십전대보탕 선택
|
||||
page.select_option('#compoundFormula', label='십전대보탕')
|
||||
time.sleep(1)
|
||||
|
||||
# 3. 새 약재 추가 버튼 클릭
|
||||
page.click('#addIngredientBtn')
|
||||
time.sleep(1)
|
||||
|
||||
# 4. 드롭다운 확인
|
||||
dropdown = page.locator('.herb-select').last
|
||||
options = dropdown.locator('option').all_text_contents()
|
||||
|
||||
print(f"드롭다운 옵션 수: {len(options)}")
|
||||
print(f"처음 5개: {options[:5]}")
|
||||
|
||||
browser.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_herb_dropdown()
|
||||
'''
|
||||
|
||||
print("Playwright 테스트 코드를 test_ui_dropdown.py 파일로 저장합니다.")
|
||||
|
||||
with open('/root/kdrug/test_ui_dropdown.py', 'w') as f:
|
||||
f.write(test_code)
|
||||
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""메인 테스트 실행"""
|
||||
print("\n" + "="*80)
|
||||
print("신규 약재 추가 드롭다운 버그 테스트")
|
||||
print("="*80)
|
||||
|
||||
# 1. API 테스트
|
||||
if not test_herb_dropdown_api():
|
||||
print("\n❌ 약재 목록 API에 문제가 있습니다")
|
||||
return
|
||||
|
||||
# 2. 처방 구성 테스트
|
||||
ingredient_codes = test_formula_ingredients()
|
||||
|
||||
# 3. 조제용 약재 테스트
|
||||
test_available_herbs_for_compound()
|
||||
|
||||
# 4. 프론트엔드 코드 분석
|
||||
check_frontend_code()
|
||||
|
||||
# 5. Playwright 테스트 생성
|
||||
test_with_playwright()
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("테스트 완료 - app.js 파일을 확인하여 문제를 찾아보겠습니다")
|
||||
print("="*80)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue
Block a user