chore: 개발 파일 정리 및 구조화
- 개발/테스트 스크립트를 dev_scripts/ 폴더로 이동 - 스크린샷을 screenshots/ 폴더로 이동 - 백업 파일 보존 (.backup) - 처방 관련 추가 스크립트 포함 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
124bc5eaf8
commit
ad9ac396e2
168
add_prescription_data.py
Normal file
168
add_prescription_data.py
Normal file
@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
처방 데이터 추가 스크립트
|
||||
- 소청룡탕, 갈근탕 등 처방 데이터 추가
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
|
||||
def get_connection():
|
||||
"""데이터베이스 연결"""
|
||||
return sqlite3.connect('database/kdrug.db')
|
||||
|
||||
def add_prescriptions():
|
||||
"""소청룡탕과 갈근탕 처방 추가"""
|
||||
conn = get_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 처방 데이터 정의
|
||||
prescriptions = [
|
||||
{
|
||||
'formula_code': 'SCR001',
|
||||
'formula_name': '소청룡탕',
|
||||
'formula_type': 'STANDARD',
|
||||
'base_cheop': 1,
|
||||
'base_pouches': 1,
|
||||
'description': '외감풍한, 내정수음으로 인한 기침, 천식을 치료하는 처방. 한담을 풀어내고 기침을 멎게 함.',
|
||||
'ingredients': [
|
||||
{'code': '3147H1AHM', 'amount': 6.0, 'notes': '발한해표'}, # 마황
|
||||
{'code': '3419H1AHM', 'amount': 6.0, 'notes': '화영지통'}, # 백작약
|
||||
{'code': '3342H1AHM', 'amount': 6.0, 'notes': '렴폐지해'}, # 오미자
|
||||
{'code': '3182H1AHM', 'amount': 6.0, 'notes': '화담지구'}, # 반하
|
||||
{'code': '3285H1AHM', 'amount': 4.0, 'notes': '온폐산한'}, # 세신
|
||||
{'code': '3017H1AHM', 'amount': 4.0, 'notes': '온중산한'}, # 건강
|
||||
{'code': '3033H1AHM', 'amount': 4.0, 'notes': '해표발한'}, # 계지
|
||||
{'code': '3007H1AHM', 'amount': 4.0, 'notes': '조화제약'}, # 감초
|
||||
]
|
||||
},
|
||||
{
|
||||
'formula_code': 'GGT001',
|
||||
'formula_name': '갈근탕',
|
||||
'formula_type': 'STANDARD',
|
||||
'base_cheop': 1,
|
||||
'base_pouches': 1,
|
||||
'description': '외감풍한으로 인한 두통, 발열, 오한, 항강을 치료하는 처방. 발한해표하고 승진해기함.',
|
||||
'ingredients': [
|
||||
{'code': '3002H1AHM', 'amount': 8.0, 'notes': '승진해기'}, # 갈근
|
||||
{'code': '3147H1AHM', 'amount': 6.0, 'notes': '발한해표'}, # 마황
|
||||
{'code': '3115H1AHM', 'amount': 6.0, 'notes': '보중익기'}, # 대조(대추)
|
||||
{'code': '3033H1AHM', 'amount': 4.0, 'notes': '해표발한'}, # 계지
|
||||
{'code': '3419H1AHM', 'amount': 4.0, 'notes': '화영지통'}, # 작약
|
||||
{'code': '3007H1AHM', 'amount': 4.0, 'notes': '조화제약'}, # 감초
|
||||
{'code': '3017H1AHM', 'amount': 2.0, 'notes': '온중산한'}, # 건강
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
try:
|
||||
for prescription in prescriptions:
|
||||
# 1. formulas 테이블에 처방 추가
|
||||
cursor.execute("""
|
||||
INSERT INTO formulas (
|
||||
formula_code, formula_name, formula_type, base_cheop, base_pouches,
|
||||
description, is_active, created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
""", (
|
||||
prescription['formula_code'],
|
||||
prescription['formula_name'],
|
||||
prescription['formula_type'],
|
||||
prescription['base_cheop'],
|
||||
prescription['base_pouches'],
|
||||
prescription['description']
|
||||
))
|
||||
|
||||
formula_id = cursor.lastrowid
|
||||
print(f"[추가됨] {prescription['formula_name']} 처방 추가 완료 (ID: {formula_id})")
|
||||
|
||||
# 2. formula_ingredients 테이블에 구성 약재 추가
|
||||
for ingredient in prescription['ingredients']:
|
||||
# 약재 이름 조회 (로그용)
|
||||
cursor.execute("""
|
||||
SELECT herb_name FROM herb_masters
|
||||
WHERE ingredient_code = ?
|
||||
""", (ingredient['code'],))
|
||||
herb_name_result = cursor.fetchone()
|
||||
herb_name = herb_name_result[0] if herb_name_result else 'Unknown'
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO formula_ingredients (
|
||||
formula_id, ingredient_code, grams_per_cheop, notes,
|
||||
sort_order, created_at
|
||||
) VALUES (?, ?, ?, ?, 0, CURRENT_TIMESTAMP)
|
||||
""", (
|
||||
formula_id,
|
||||
ingredient['code'],
|
||||
ingredient['amount'],
|
||||
ingredient['notes']
|
||||
))
|
||||
|
||||
print(f" - {herb_name}({ingredient['code']}): {ingredient['amount']}g - {ingredient['notes']}")
|
||||
|
||||
conn.commit()
|
||||
print("\n[완료] 모든 처방 데이터가 성공적으로 추가되었습니다!")
|
||||
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
print(f"\n[오류] 오류 발생: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def verify_prescriptions():
|
||||
"""추가된 처방 데이터 확인"""
|
||||
conn = get_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("추가된 처방 데이터 확인")
|
||||
print("="*80)
|
||||
|
||||
# 추가된 처방 목록 확인
|
||||
cursor.execute("""
|
||||
SELECT f.formula_id, f.formula_code, f.formula_name, f.formula_type, f.description,
|
||||
COUNT(fi.ingredient_id) as ingredient_count,
|
||||
SUM(fi.grams_per_cheop) as total_amount
|
||||
FROM formulas f
|
||||
LEFT JOIN formula_ingredients fi ON f.formula_id = fi.formula_id
|
||||
WHERE f.formula_code IN ('SCR001', 'GGT001')
|
||||
GROUP BY f.formula_id
|
||||
""")
|
||||
|
||||
for row in cursor.fetchall():
|
||||
print(f"\n[처방] {row[2]} ({row[1]})")
|
||||
print(f" 타입: {row[3]}")
|
||||
print(f" 설명: {row[4]}")
|
||||
print(f" 구성약재: {row[5]}가지")
|
||||
print(f" 총 용량: {row[6]}g")
|
||||
|
||||
# 구성 약재 상세
|
||||
cursor.execute("""
|
||||
SELECT hm.herb_name, fi.ingredient_code, fi.grams_per_cheop, fi.notes
|
||||
FROM formula_ingredients fi
|
||||
JOIN herb_masters hm ON fi.ingredient_code = hm.ingredient_code
|
||||
WHERE fi.formula_id = ?
|
||||
ORDER BY fi.grams_per_cheop DESC
|
||||
""", (row[0],))
|
||||
|
||||
print(" 구성 약재:")
|
||||
for ingredient in cursor.fetchall():
|
||||
print(f" - {ingredient[0]}({ingredient[1]}): {ingredient[2]}g - {ingredient[3]}")
|
||||
|
||||
conn.close()
|
||||
|
||||
def main():
|
||||
print("="*80)
|
||||
print("처방 데이터 추가 스크립트")
|
||||
print("="*80)
|
||||
|
||||
# 처방 추가
|
||||
add_prescriptions()
|
||||
|
||||
# 추가된 데이터 확인
|
||||
verify_prescriptions()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,8 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
한약재 샘플 데이터 추가 스크립트
|
||||
주요 약재들의 확장 정보와 효능 태그를 추가합니다.
|
||||
한약재 샘플 데이터 추가 - 십전대보탕 구성 약재
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
@ -17,171 +16,173 @@ def add_herb_extended_data():
|
||||
conn = get_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 주요 약재들의 확장 정보
|
||||
herbs_data = [
|
||||
# 십전대보탕 구성 약재들의 실제 ingredient_code 사용
|
||||
herb_data = [
|
||||
{
|
||||
'ingredient_code': '3400H1AHM', # 인삼
|
||||
'property': '온',
|
||||
'taste': '감,미고',
|
||||
'meridian_tropism': '비,폐,심',
|
||||
'main_effects': '대보원기, 보비익폐, 생진지갈, 안신증지',
|
||||
'indications': '기허증, 비허증, 폐허증, 심기허증, 진액부족, 당뇨병',
|
||||
'contraindications': '실증, 열증, 음허화왕',
|
||||
'precautions': '복용 중 무 섭취 금지, 고혈압 환자 주의',
|
||||
'dosage_range': '3-9g',
|
||||
'dosage_max': '30g',
|
||||
'active_compounds': '인삼사포닌(ginsenoside Rb1, Rg1, Rg3), 다당체, 아미노산',
|
||||
'pharmacological_effects': '면역증강, 항피로, 항산화, 혈당조절, 인지능력개선',
|
||||
'clinical_applications': '만성피로, 면역력저하, 당뇨병 보조치료, 노인성 인지저하',
|
||||
'tags': [('보기', 5), ('보양', 4), ('안신', 3), ('진통', 2)]
|
||||
'property': '온(溫)',
|
||||
'taste': '감(甘), 미고(微苦)',
|
||||
'meridian_tropism': '폐(肺), 비(脾), 심(心)',
|
||||
'main_effects': '대보원기, 보비익폐, 생진지갈, 안신익지',
|
||||
'indications': '기허증, 피로, 식욕부진, 설사, 호흡곤란, 자한, 양위, 소갈, 건망, 불면',
|
||||
'dosage_range': '1~3돈(3~9g)',
|
||||
'precautions': '실증, 열증자 신중 투여',
|
||||
'preparation_method': '수치법: 홍삼, 백삼, 당삼 등으로 가공',
|
||||
'tags': [
|
||||
('보기', 5),
|
||||
('보혈', 3),
|
||||
('안신', 4),
|
||||
]
|
||||
},
|
||||
{
|
||||
'ingredient_code': '3400H1ADL', # 감초
|
||||
'property': '평',
|
||||
'taste': '감',
|
||||
'meridian_tropism': '비,위,폐,심',
|
||||
'main_effects': '보비익기, 청열해독, 거담지해, 완급지통, 조화제약',
|
||||
'indications': '비허증, 해수, 인후통, 소화성궤양, 경련성 통증',
|
||||
'contraindications': '습증, 수종, 고혈압',
|
||||
'precautions': '장기복용 시 부종 주의, 칼륨 감소 주의',
|
||||
'dosage_range': '2-10g',
|
||||
'dosage_max': '30g',
|
||||
'active_compounds': 'glycyrrhizin, liquiritin, flavonoid, triterpenoid',
|
||||
'pharmacological_effects': '항염증, 항궤양, 간보호, 진해거담, 항알레르기',
|
||||
'clinical_applications': '위염, 위궤양, 기관지염, 약물조화, 간염',
|
||||
'tags': [('보기', 3), ('청열', 3), ('해독', 4), ('거담', 3), ('항염', 4)]
|
||||
'ingredient_code': '3007H1AHM', # 감초
|
||||
'property': '평(平)',
|
||||
'taste': '감(甘)',
|
||||
'meridian_tropism': '심(心), 폐(肺), 비(脾), 위(胃)',
|
||||
'main_effects': '화중완급, 윤폐지해, 해독',
|
||||
'indications': '복통, 기침, 인후통, 소화불량, 약물중독',
|
||||
'dosage_range': '1~3돈(3~9g)',
|
||||
'precautions': '장기복용시 부종 주의',
|
||||
'preparation_method': '자감초(炙甘草) 등',
|
||||
'tags': [
|
||||
('보기', 3),
|
||||
('해독', 4),
|
||||
('윤조', 3),
|
||||
('청열', 2),
|
||||
('항염', 3),
|
||||
]
|
||||
},
|
||||
{
|
||||
'ingredient_code': '3400H1ACD', # 당귀
|
||||
'property': '온',
|
||||
'taste': '감,신',
|
||||
'meridian_tropism': '간,심,비',
|
||||
'main_effects': '보혈활혈, 조경지통, 윤장통변',
|
||||
'indications': '혈허증, 월경부조, 무월경, 변비, 타박상',
|
||||
'contraindications': '설사, 습성체질',
|
||||
'precautions': '과량 복용 시 설사 주의',
|
||||
'dosage_range': '5-15g',
|
||||
'dosage_max': '30g',
|
||||
'active_compounds': 'ligustilide, n-butylidene phthalide, ferulic acid',
|
||||
'pharmacological_effects': '혈액순환개선, 항혈전, 자궁수축조절, 진정진통',
|
||||
'clinical_applications': '빈혈, 월경불순, 산후조리, 혈액순환장애',
|
||||
'tags': [('보혈', 5), ('활혈', 5), ('진통', 3)]
|
||||
'ingredient_code': '3204H1AHM', # 백출
|
||||
'property': '온(溫)',
|
||||
'taste': '감(甘), 고(苦)',
|
||||
'meridian_tropism': '비(脾), 위(胃)',
|
||||
'main_effects': '건비익기, 조습이수, 지한, 안태',
|
||||
'indications': '비허설사, 수종, 담음, 자한, 태동불안',
|
||||
'dosage_range': '2~4돈(6~12g)',
|
||||
'precautions': '음허내열자 신중',
|
||||
'preparation_method': '토백출, 생백출',
|
||||
'tags': [
|
||||
('보기', 4),
|
||||
('이수', 4),
|
||||
('건비', 5),
|
||||
]
|
||||
},
|
||||
{
|
||||
'ingredient_code': '3400H1AGN', # 황기
|
||||
'property': '온',
|
||||
'taste': '감',
|
||||
'meridian_tropism': '비,폐',
|
||||
'main_effects': '보기승양, 고표지한, 이수소종, 탁독배농',
|
||||
'indications': '기허증, 자한, 부종, 탈항, 자궁탈수',
|
||||
'contraindications': '표실증, 음허화왕',
|
||||
'precautions': '감기 초기 금지',
|
||||
'dosage_range': '10-30g',
|
||||
'dosage_max': '60g',
|
||||
'active_compounds': 'astragaloside, polysaccharide, flavonoid',
|
||||
'pharmacological_effects': '면역조절, 항바이러스, 항산화, 신기능보호',
|
||||
'clinical_applications': '면역력저하, 만성신장염, 당뇨병, 심부전',
|
||||
'tags': [('보기', 5), ('이수', 3), ('해표', 2)]
|
||||
'ingredient_code': '3215H1AHM', # 복령
|
||||
'property': '평(平)',
|
||||
'taste': '감(甘), 담(淡)',
|
||||
'meridian_tropism': '심(心), 폐(肺), 비(脾), 신(腎)',
|
||||
'main_effects': '이수삼습, 건비영심, 안신',
|
||||
'indications': '소변불리, 수종, 설사, 불면, 심계',
|
||||
'dosage_range': '3~5돈(9~15g)',
|
||||
'precautions': '음허자 신중',
|
||||
'preparation_method': '백복령, 적복령',
|
||||
'tags': [
|
||||
('이수', 5),
|
||||
('안신', 3),
|
||||
('건비', 3),
|
||||
]
|
||||
},
|
||||
{
|
||||
'ingredient_code': '3400H1AEW', # 작약
|
||||
'property': '량',
|
||||
'taste': '고,산',
|
||||
'meridian_tropism': '간,비',
|
||||
'main_effects': '양혈조경, 유간지통, 렴음지한',
|
||||
'indications': '혈허증, 월경부조, 간혈부족, 자한도한',
|
||||
'contraindications': '양허설사',
|
||||
'precautions': '한성약물과 병용 주의',
|
||||
'dosage_range': '6-15g',
|
||||
'dosage_max': '30g',
|
||||
'active_compounds': 'paeoniflorin, albiflorin, benzoic acid',
|
||||
'pharmacological_effects': '진정진통, 항경련, 항염증, 면역조절',
|
||||
'clinical_applications': '월경통, 근육경련, 두통, 자가면역질환',
|
||||
'tags': [('보혈', 4), ('평간', 4), ('진통', 4)]
|
||||
'ingredient_code': '3419H1AHM', # 작약
|
||||
'property': '미한(微寒)',
|
||||
'taste': '고(苦), 산(酸)',
|
||||
'meridian_tropism': '간(肝), 비(脾)',
|
||||
'main_effects': '양혈렴음, 유간지통, 평간양',
|
||||
'indications': '혈허, 복통, 사지경련, 두훈, 월경불순',
|
||||
'dosage_range': '2~4돈(6~12g)',
|
||||
'precautions': '비허설사자 신중',
|
||||
'preparation_method': '백작약, 적작약',
|
||||
'tags': [
|
||||
('보혈', 4),
|
||||
('진경', 4),
|
||||
('평간', 3),
|
||||
]
|
||||
},
|
||||
{
|
||||
'ingredient_code': '3400H1ACF', # 천궁
|
||||
'property': '온',
|
||||
'taste': '신',
|
||||
'meridian_tropism': '간,담,심포',
|
||||
'ingredient_code': '3475H1AHM', # 천궁
|
||||
'property': '온(溫)',
|
||||
'taste': '신(辛)',
|
||||
'meridian_tropism': '간(肝), 담(膽), 심포(心包)',
|
||||
'main_effects': '활혈행기, 거풍지통',
|
||||
'indications': '혈어증, 두통, 월경불순, 풍습비통',
|
||||
'contraindications': '음허화왕, 월경과다',
|
||||
'precautions': '출혈 경향 환자 주의',
|
||||
'dosage_range': '3-10g',
|
||||
'dosage_max': '15g',
|
||||
'active_compounds': 'ligustilide, senkyunolide, ferulic acid',
|
||||
'pharmacological_effects': '혈관확장, 항혈전, 진정진통, 항염증',
|
||||
'clinical_applications': '편두통, 혈관성 두통, 어혈증, 월경통',
|
||||
'tags': [('활혈', 5), ('이기', 4), ('진통', 5)]
|
||||
'indications': '혈체, 두통, 현훈, 월경불순, 복통',
|
||||
'dosage_range': '1~2돈(3~6g)',
|
||||
'precautions': '음허화왕자 신중',
|
||||
'preparation_method': '주천궁',
|
||||
'tags': [
|
||||
('활혈', 5),
|
||||
('거풍', 3),
|
||||
('지통', 4),
|
||||
]
|
||||
},
|
||||
{
|
||||
'ingredient_code': '3400H1ACG', # 지황(숙지황)
|
||||
'property': '온',
|
||||
'taste': '감',
|
||||
'meridian_tropism': '간,신',
|
||||
'main_effects': '보혈자음, 익정전수',
|
||||
'indications': '혈허증, 간신음허, 수발조백, 유정도한',
|
||||
'contraindications': '비허설사, 담습',
|
||||
'precautions': '소화불량 주의',
|
||||
'dosage_range': '10-30g',
|
||||
'dosage_max': '60g',
|
||||
'active_compounds': 'catalpol, rehmannioside, aucubin',
|
||||
'pharmacological_effects': '조혈촉진, 면역조절, 혈당강하, 신경보호',
|
||||
'clinical_applications': '빈혈, 당뇨병, 치매예방, 불임증',
|
||||
'tags': [('보혈', 5), ('보음', 5)]
|
||||
'ingredient_code': '3105H1AHM', # 당귀
|
||||
'property': '온(溫)',
|
||||
'taste': '감(甘), 신(辛)',
|
||||
'meridian_tropism': '간(肝), 심(心), 비(脾)',
|
||||
'main_effects': '보혈활혈, 조경지통, 윤장통변',
|
||||
'indications': '혈허, 월경불순, 복통, 변비, 타박상',
|
||||
'dosage_range': '2~4돈(6~12g)',
|
||||
'precautions': '습성설사자 신중',
|
||||
'preparation_method': '주당귀, 당귀신, 당귀미',
|
||||
'tags': [
|
||||
('보혈', 5),
|
||||
('활혈', 4),
|
||||
('윤조', 3),
|
||||
]
|
||||
},
|
||||
{
|
||||
'ingredient_code': '3400H1AFJ', # 백출
|
||||
'property': '온',
|
||||
'taste': '고,감',
|
||||
'meridian_tropism': '비,위',
|
||||
'main_effects': '건비익기, 조습이수, 지한안태',
|
||||
'indications': '비허증, 식욕부진, 설사, 수종, 자한',
|
||||
'contraindications': '음허조갈',
|
||||
'precautions': '진액부족 시 주의',
|
||||
'dosage_range': '6-15g',
|
||||
'dosage_max': '30g',
|
||||
'active_compounds': 'atractylenolide, atractylon',
|
||||
'pharmacological_effects': '위장운동촉진, 이뇨, 항염증, 항종양',
|
||||
'clinical_applications': '만성설사, 부종, 임신오조',
|
||||
'tags': [('보기', 4), ('이수', 4), ('소화', 3)]
|
||||
'ingredient_code': '3583H1AHM', # 황기
|
||||
'property': '온(溫)',
|
||||
'taste': '감(甘)',
|
||||
'meridian_tropism': '폐(肺), 비(脾)',
|
||||
'main_effects': '보기승양, 고표지한, 이수소종, 탈독생기',
|
||||
'indications': '기허, 자한, 설사, 탈항, 수종, 창양',
|
||||
'dosage_range': '3~6돈(9~18g)',
|
||||
'precautions': '표실사 및 음허자 신중',
|
||||
'preparation_method': '밀자황기',
|
||||
'tags': [
|
||||
('보기', 5),
|
||||
('승양', 4),
|
||||
('고표', 4),
|
||||
]
|
||||
},
|
||||
{
|
||||
'ingredient_code': '3400H1AGM', # 복령
|
||||
'property': '평',
|
||||
'taste': '감,담',
|
||||
'meridian_tropism': '심,비,폐,신',
|
||||
'main_effects': '이수삼습, 건비안신',
|
||||
'indications': '수종, 소변불리, 비허설사, 불면, 심계',
|
||||
'contraindications': '음허진액부족',
|
||||
'precautions': '이뇨제와 병용 주의',
|
||||
'dosage_range': '10-15g',
|
||||
'dosage_max': '30g',
|
||||
'active_compounds': 'pachymic acid, polysaccharide',
|
||||
'pharmacological_effects': '이뇨, 진정, 항염증, 면역조절',
|
||||
'clinical_applications': '부종, 불면증, 만성설사',
|
||||
'tags': [('이수', 5), ('안신', 3), ('보기', 2)]
|
||||
'ingredient_code': '3384H1AHM', # 육계
|
||||
'property': '대열(大熱)',
|
||||
'taste': '감(甘), 신(辛)',
|
||||
'meridian_tropism': '신(腎), 비(脾), 심(心), 간(肝)',
|
||||
'main_effects': '보화조양, 산한지통, 온경통맥',
|
||||
'indications': '양허, 냉증, 요통, 복통, 설사',
|
||||
'dosage_range': '0.5~1돈(1.5~3g)',
|
||||
'precautions': '음허화왕자, 임신부 금기',
|
||||
'preparation_method': '육계심, 계피',
|
||||
'tags': [
|
||||
('보양', 5),
|
||||
('온리', 5),
|
||||
('산한', 4),
|
||||
]
|
||||
},
|
||||
{
|
||||
'ingredient_code': '3400H1AGI', # 반하
|
||||
'property': '온',
|
||||
'taste': '신',
|
||||
'meridian_tropism': '비,위,폐',
|
||||
'main_effects': '조습화담, 강역지구, 소비산결',
|
||||
'indications': '습담, 구토, 해수담다, 현훈',
|
||||
'contraindications': '음허조해, 임신',
|
||||
'precautions': '임산부 금기, 생품 독성 주의',
|
||||
'dosage_range': '5-10g',
|
||||
'dosage_max': '15g',
|
||||
'active_compounds': 'ephedrine, β-sitosterol',
|
||||
'pharmacological_effects': '진토, 진해거담, 항종양',
|
||||
'clinical_applications': '임신오조, 기관지염, 현훈증',
|
||||
'tags': [('거담', 5), ('소화', 3)]
|
||||
}
|
||||
'ingredient_code': '3299H1AHM', # 숙지황
|
||||
'property': '온(溫)',
|
||||
'taste': '감(甘)',
|
||||
'meridian_tropism': '간(肝), 신(腎)',
|
||||
'main_effects': '자음보혈, 익정전수',
|
||||
'indications': '혈허, 음허, 요슬산연, 유정, 붕루',
|
||||
'dosage_range': '3~6돈(9~18g)',
|
||||
'precautions': '비허설사, 담다자 신중',
|
||||
'preparation_method': '숙지황 제법',
|
||||
'tags': [
|
||||
('보혈', 5),
|
||||
('자음', 5),
|
||||
('보신', 4),
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
for herb in herbs_data:
|
||||
for herb in herb_data:
|
||||
# herb_master_extended 업데이트
|
||||
cursor.execute("""
|
||||
UPDATE herb_master_extended
|
||||
@ -190,53 +191,49 @@ def add_herb_extended_data():
|
||||
meridian_tropism = ?,
|
||||
main_effects = ?,
|
||||
indications = ?,
|
||||
contraindications = ?,
|
||||
precautions = ?,
|
||||
dosage_range = ?,
|
||||
dosage_max = ?,
|
||||
active_compounds = ?,
|
||||
pharmacological_effects = ?,
|
||||
clinical_applications = ?,
|
||||
precautions = ?,
|
||||
preparation_method = ?,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE ingredient_code = ?
|
||||
""", (
|
||||
herb['property'], herb['taste'], herb['meridian_tropism'],
|
||||
herb['main_effects'], herb['indications'], herb['contraindications'],
|
||||
herb['precautions'], herb['dosage_range'], herb['dosage_max'],
|
||||
herb['active_compounds'], herb['pharmacological_effects'],
|
||||
herb['clinical_applications'], herb['ingredient_code']
|
||||
herb['property'],
|
||||
herb['taste'],
|
||||
herb['meridian_tropism'],
|
||||
herb['main_effects'],
|
||||
herb['indications'],
|
||||
herb['dosage_range'],
|
||||
herb['precautions'],
|
||||
herb['preparation_method'],
|
||||
herb['ingredient_code']
|
||||
))
|
||||
|
||||
# herb_id 조회
|
||||
cursor.execute("""
|
||||
SELECT herb_id FROM herb_master_extended
|
||||
WHERE ingredient_code = ?
|
||||
""", (herb['ingredient_code'],))
|
||||
# 효능 태그 매핑
|
||||
for tag_name, strength in herb.get('tags', []):
|
||||
# 태그 ID 조회
|
||||
cursor.execute("""
|
||||
SELECT tag_id FROM herb_efficacy_tags
|
||||
WHERE tag_name = ?
|
||||
""", (tag_name,))
|
||||
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
herb_id = result[0]
|
||||
tag_result = cursor.fetchone()
|
||||
if tag_result:
|
||||
tag_id = tag_result[0]
|
||||
|
||||
# 효능 태그 매핑
|
||||
for tag_name, strength in herb.get('tags', []):
|
||||
# 태그 ID 조회
|
||||
# 기존 태그 삭제
|
||||
cursor.execute("""
|
||||
SELECT tag_id FROM herb_efficacy_tags
|
||||
WHERE tag_name = ?
|
||||
""", (tag_name,))
|
||||
DELETE FROM herb_item_tags
|
||||
WHERE ingredient_code = ? AND tag_id = ?
|
||||
""", (herb['ingredient_code'], tag_id))
|
||||
|
||||
tag_result = cursor.fetchone()
|
||||
if tag_result:
|
||||
tag_id = tag_result[0]
|
||||
# 태그 매핑 추가
|
||||
cursor.execute("""
|
||||
INSERT INTO herb_item_tags
|
||||
(ingredient_code, tag_id, strength)
|
||||
VALUES (?, ?, ?)
|
||||
""", (herb['ingredient_code'], tag_id, strength))
|
||||
|
||||
# 태그 매핑 추가
|
||||
cursor.execute("""
|
||||
INSERT OR REPLACE INTO herb_item_tags
|
||||
(herb_id, tag_id, strength)
|
||||
VALUES (?, ?, ?)
|
||||
""", (herb_id, tag_id, strength))
|
||||
|
||||
print(f"✅ {herb['ingredient_code']} 데이터 추가 완료")
|
||||
print(f"✅ {herb['ingredient_code']} 데이터 추가 완료")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
@ -248,85 +245,64 @@ def add_prescription_rules():
|
||||
|
||||
# 몇 가지 대표적인 배합 규칙 추가
|
||||
rules = [
|
||||
# 상수(相須) - 서로 도와서 효과를 증강
|
||||
{
|
||||
'herb1': '인삼', 'herb2': '황기',
|
||||
'relationship': '상수',
|
||||
'description': '두 약재가 함께 사용되면 보기 효과가 증강됨',
|
||||
'severity': 0
|
||||
'herb1': '인삼',
|
||||
'herb2': '황기',
|
||||
'rule_type': '상수',
|
||||
'description': '보기작용 상승효과',
|
||||
'clinical_note': '기허증에 병용시 효과 증대'
|
||||
},
|
||||
{
|
||||
'herb1': '당귀', 'herb2': '천궁',
|
||||
'relationship': '상수',
|
||||
'description': '혈액순환 개선 효과가 증강됨',
|
||||
'severity': 0
|
||||
},
|
||||
# 상사(相使) - 한 약이 다른 약의 효능을 도움
|
||||
{
|
||||
'herb1': '반하', 'herb2': '생강',
|
||||
'relationship': '상사',
|
||||
'description': '생강이 반하의 독성을 감소시킴',
|
||||
'severity': 0
|
||||
},
|
||||
# 상반(相反) - 함께 사용하면 독성이나 부작용 발생
|
||||
{
|
||||
'herb1': '감초', 'herb2': '감수',
|
||||
'relationship': '상반',
|
||||
'description': '십팔반(十八反) - 함께 사용 금기',
|
||||
'severity': 5,
|
||||
'is_absolute': True
|
||||
'herb1': '당귀',
|
||||
'herb2': '천궁',
|
||||
'rule_type': '상수',
|
||||
'description': '활혈작용 상승효과',
|
||||
'clinical_note': '혈허, 혈체에 병용'
|
||||
},
|
||||
{
|
||||
'herb1': '인삼', 'herb2': '오령지',
|
||||
'relationship': '상반',
|
||||
'description': '십구외(十九畏) - 함께 사용 주의',
|
||||
'severity': 4,
|
||||
'is_absolute': False
|
||||
'herb1': '반하',
|
||||
'herb2': '생강',
|
||||
'rule_type': '상수',
|
||||
'description': '반하의 독성 감소, 진토작용 증강',
|
||||
'clinical_note': '구토, 오심에 병용'
|
||||
},
|
||||
{
|
||||
'herb1': '감초',
|
||||
'herb2': '감수',
|
||||
'rule_type': '상반',
|
||||
'description': '효능 상반',
|
||||
'clinical_note': '병용 금지'
|
||||
},
|
||||
{
|
||||
'herb1': '인삼',
|
||||
'herb2': '오령지',
|
||||
'rule_type': '상외',
|
||||
'description': '효능 감소',
|
||||
'clinical_note': '병용시 주의'
|
||||
}
|
||||
]
|
||||
|
||||
for rule in rules:
|
||||
# herb_id 조회
|
||||
cursor.execute("""
|
||||
SELECT herb_id FROM herb_master_extended
|
||||
WHERE name_korean = ?
|
||||
""", (rule['herb1'],))
|
||||
herb1_result = cursor.fetchone()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT herb_id FROM herb_master_extended
|
||||
WHERE name_korean = ?
|
||||
""", (rule['herb2'],))
|
||||
herb2_result = cursor.fetchone()
|
||||
|
||||
if herb1_result and herb2_result:
|
||||
cursor.execute("""
|
||||
INSERT OR REPLACE INTO prescription_rules
|
||||
(herb1_id, herb2_id, relationship_type, description,
|
||||
severity_level, is_absolute)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""", (
|
||||
herb1_result[0], herb2_result[0],
|
||||
rule['relationship'], rule['description'],
|
||||
rule['severity'], rule.get('is_absolute', False)
|
||||
))
|
||||
print(f"✅ {rule['herb1']} - {rule['herb2']} 규칙 추가")
|
||||
INSERT OR IGNORE INTO prescription_rules
|
||||
(herb1_name, herb2_name, rule_type, description, clinical_notes)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""", (rule['herb1'], rule['herb2'], rule['rule_type'],
|
||||
rule['description'], rule['clinical_note']))
|
||||
print(f"✅ {rule['herb1']} - {rule['herb2']} 규칙 추가")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def main():
|
||||
"""메인 실행 함수"""
|
||||
print("\n" + "="*80)
|
||||
print("한약재 샘플 데이터 추가")
|
||||
print("="*80 + "\n")
|
||||
print("=" * 80)
|
||||
print("한약재 샘플 데이터 추가 - 십전대보탕 구성 약재")
|
||||
print("=" * 80)
|
||||
|
||||
try:
|
||||
# 1. 약재 확장 정보 및 태그 추가
|
||||
print("1. 약재 확장 정보 추가 중...")
|
||||
print("\n1. 약재 확장 정보 추가 중...")
|
||||
add_herb_extended_data()
|
||||
|
||||
# 2. 처방 규칙 추가
|
||||
print("\n2. 처방 배합 규칙 추가 중...")
|
||||
add_prescription_rules()
|
||||
|
||||
|
||||
180
add_wolbitang_prescriptions.py
Normal file
180
add_wolbitang_prescriptions.py
Normal file
@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
월비탕 단계별 처방 추가 스크립트
|
||||
|
||||
월비탕 1차부터 4차까지 단계별로 처방을 등록합니다.
|
||||
각 단계마다 약재의 용량이 다릅니다.
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
def add_wolbitang_prescriptions():
|
||||
"""월비탕 단계별 처방 추가"""
|
||||
|
||||
# 월비탕 단계별 데이터
|
||||
wolbitang_data = {
|
||||
"월비탕 1차": {
|
||||
"마황": 4,
|
||||
"석고": 3,
|
||||
"감초": 3,
|
||||
"진피": 3.333,
|
||||
"복령": 4,
|
||||
"갈근": 3.333,
|
||||
"건지황": 3.333,
|
||||
"창출": 3.333
|
||||
},
|
||||
"월비탕 2차": {
|
||||
"마황": 5,
|
||||
"석고": 4,
|
||||
"감초": 3,
|
||||
"진피": 3.75,
|
||||
"복령": 4,
|
||||
"갈근": 3.333,
|
||||
"건지황": 3.333,
|
||||
"창출": 3.333
|
||||
},
|
||||
"월비탕 3차": {
|
||||
"마황": 6,
|
||||
"석고": 4.17,
|
||||
"감초": 3,
|
||||
"진피": 4.17,
|
||||
"복령": 4.17,
|
||||
"갈근": 3.75,
|
||||
"건지황": 3.75,
|
||||
"창출": 3.333
|
||||
},
|
||||
"월비탕 4차": {
|
||||
"마황": 7,
|
||||
"석고": 5,
|
||||
"감초": 3,
|
||||
"진피": 4.17,
|
||||
"복령": 5,
|
||||
"갈근": 3.75,
|
||||
"건지황": 4,
|
||||
"창출": 3.333
|
||||
}
|
||||
}
|
||||
|
||||
conn = sqlite3.connect('kdrug.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
# 약재명-코드 매핑
|
||||
herb_code_mapping = {
|
||||
"마황": "H004",
|
||||
"석고": "H025",
|
||||
"감초": "H001",
|
||||
"진피": "H022",
|
||||
"복령": "H010",
|
||||
"갈근": "H024",
|
||||
"건지황": "H026",
|
||||
"창출": "H014"
|
||||
}
|
||||
|
||||
# 각 단계별로 처방 추가
|
||||
for prescription_name, herbs in wolbitang_data.items():
|
||||
print(f"\n{'='*50}")
|
||||
print(f"{prescription_name} 추가 중...")
|
||||
|
||||
# 1. 처방 기본 정보 추가
|
||||
cursor.execute("""
|
||||
INSERT INTO prescriptions (
|
||||
name,
|
||||
description,
|
||||
source,
|
||||
category,
|
||||
created_at
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
""", (
|
||||
prescription_name,
|
||||
f"{prescription_name} - 월비탕의 단계별 처방",
|
||||
"임상처방",
|
||||
"단계별처방",
|
||||
datetime.now().isoformat()
|
||||
))
|
||||
|
||||
prescription_id = cursor.lastrowid
|
||||
print(f" 처방 ID {prescription_id}로 등록됨")
|
||||
|
||||
# 2. 처방 구성 약재 추가
|
||||
ingredients = []
|
||||
for herb_name, amount in herbs.items():
|
||||
herb_code = herb_code_mapping.get(herb_name)
|
||||
if not herb_code:
|
||||
print(f" ⚠️ {herb_name}의 코드를 찾을 수 없습니다.")
|
||||
continue
|
||||
|
||||
# prescription_ingredients 테이블에 추가
|
||||
cursor.execute("""
|
||||
INSERT INTO prescription_ingredients (
|
||||
prescription_id,
|
||||
ingredient_code,
|
||||
amount,
|
||||
unit
|
||||
) VALUES (?, ?, ?, ?)
|
||||
""", (prescription_id, herb_code, amount, 'g'))
|
||||
|
||||
ingredients.append({
|
||||
'code': herb_code,
|
||||
'name': herb_name,
|
||||
'amount': amount,
|
||||
'unit': 'g'
|
||||
})
|
||||
|
||||
print(f" - {herb_name}({herb_code}): {amount}g 추가됨")
|
||||
|
||||
# 3. prescription_details 테이블에 JSON 형태로도 저장
|
||||
cursor.execute("""
|
||||
INSERT INTO prescription_details (
|
||||
prescription_id,
|
||||
ingredients_json,
|
||||
total_herbs,
|
||||
default_packets,
|
||||
preparation_method
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
""", (
|
||||
prescription_id,
|
||||
json.dumps(ingredients, ensure_ascii=False),
|
||||
len(ingredients),
|
||||
20, # 기본 첩수
|
||||
"1일 2회, 1회 1포"
|
||||
))
|
||||
|
||||
print(f" ✅ {prescription_name} 처방 추가 완료 (총 {len(ingredients)}개 약재)")
|
||||
|
||||
conn.commit()
|
||||
print(f"\n{'='*50}")
|
||||
print("✅ 월비탕 1차~4차 처방이 모두 성공적으로 추가되었습니다!")
|
||||
|
||||
# 추가된 처방 확인
|
||||
print("\n📊 추가된 처방 목록:")
|
||||
cursor.execute("""
|
||||
SELECT p.id, p.name, pd.total_herbs
|
||||
FROM prescriptions p
|
||||
LEFT JOIN prescription_details pd ON p.id = pd.prescription_id
|
||||
WHERE p.name LIKE '월비탕%'
|
||||
ORDER BY p.id
|
||||
""")
|
||||
|
||||
for row in cursor.fetchall():
|
||||
print(f" ID {row[0]}: {row[1]} - {row[2]}개 약재")
|
||||
|
||||
except sqlite3.Error as e:
|
||||
print(f"❌ 데이터베이스 오류: {e}")
|
||||
conn.rollback()
|
||||
return False
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🌿 월비탕 단계별 처방 추가 프로그램")
|
||||
print("="*50)
|
||||
|
||||
if add_wolbitang_prescriptions():
|
||||
print("\n✅ 월비탕 처방 추가 작업이 완료되었습니다.")
|
||||
else:
|
||||
print("\n❌ 처방 추가 중 오류가 발생했습니다.")
|
||||
244
dev_scripts/analyze_inventory_discrepancy.py
Normal file
244
dev_scripts/analyze_inventory_discrepancy.py
Normal file
@ -0,0 +1,244 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
재고 자산 금액 불일치 분석 스크립트
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
from decimal import Decimal, getcontext
|
||||
|
||||
# Decimal 정밀도 설정
|
||||
getcontext().prec = 10
|
||||
|
||||
def analyze_inventory_discrepancy():
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
print("=" * 80)
|
||||
print("재고 자산 금액 불일치 분석")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
# 1. 현재 inventory_lots 기준 재고 자산 계산
|
||||
print("1. 현재 시스템 재고 자산 계산 (inventory_lots 기준)")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
SUM(quantity_onhand * unit_price_per_g) as total_value,
|
||||
COUNT(*) as lot_count,
|
||||
SUM(quantity_onhand) as total_quantity
|
||||
FROM inventory_lots
|
||||
WHERE is_depleted = 0 AND quantity_onhand > 0
|
||||
""")
|
||||
|
||||
result = cursor.fetchone()
|
||||
system_total = result['total_value'] or 0
|
||||
|
||||
print(f" 총 재고 자산: ₩{system_total:,.0f}")
|
||||
print(f" 총 LOT 수: {result['lot_count']}개")
|
||||
print(f" 총 재고량: {result['total_quantity']:,.1f}g")
|
||||
print()
|
||||
|
||||
# 2. 원본 입고장 데이터 분석
|
||||
print("2. 입고장 기준 계산")
|
||||
print("-" * 60)
|
||||
|
||||
# 전체 입고 금액
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
SUM(total_price) as total_purchase,
|
||||
COUNT(*) as receipt_count,
|
||||
SUM(quantity_g) as total_quantity
|
||||
FROM purchase_receipts
|
||||
""")
|
||||
|
||||
receipts = cursor.fetchone()
|
||||
total_purchase = receipts['total_purchase'] or 0
|
||||
|
||||
print(f" 총 입고 금액: ₩{total_purchase:,.0f}")
|
||||
print(f" 총 입고장 수: {receipts['receipt_count']}건")
|
||||
print(f" 총 입고량: {receipts['total_quantity']:,.1f}g")
|
||||
print()
|
||||
|
||||
# 3. 출고 데이터 분석
|
||||
print("3. 출고 데이터 분석")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
SUM(pd.quantity * il.unit_price_per_g) as total_dispensed_value,
|
||||
SUM(pd.quantity) as total_dispensed_quantity,
|
||||
COUNT(DISTINCT p.prescription_id) as prescription_count
|
||||
FROM prescription_details pd
|
||||
JOIN prescriptions p ON pd.prescription_id = p.prescription_id
|
||||
JOIN inventory_lots il ON pd.lot_id = il.lot_id
|
||||
WHERE p.status IN ('completed', 'dispensed')
|
||||
""")
|
||||
|
||||
dispensed = cursor.fetchone()
|
||||
total_dispensed_value = dispensed['total_dispensed_value'] or 0
|
||||
|
||||
print(f" 총 출고 금액: ₩{total_dispensed_value:,.0f}")
|
||||
print(f" 총 출고량: {dispensed['total_dispensed_quantity'] or 0:,.1f}g")
|
||||
print(f" 총 처방전 수: {dispensed['prescription_count']}건")
|
||||
print()
|
||||
|
||||
# 4. 재고 보정 데이터 분석
|
||||
print("4. 재고 보정 데이터 분석")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
adjustment_type,
|
||||
SUM(quantity) as total_quantity,
|
||||
SUM(quantity * unit_price) as total_value,
|
||||
COUNT(*) as count
|
||||
FROM stock_adjustments
|
||||
GROUP BY adjustment_type
|
||||
""")
|
||||
|
||||
adjustments = cursor.fetchall()
|
||||
total_adjustment_value = 0
|
||||
|
||||
for adj in adjustments:
|
||||
adj_type = adj['adjustment_type']
|
||||
value = adj['total_value'] or 0
|
||||
|
||||
# 보정 타입에 따른 금액 계산
|
||||
if adj_type in ['disposal', 'loss', 'decrease']:
|
||||
total_adjustment_value -= value
|
||||
print(f" {adj_type}: -₩{value:,.0f} ({adj['count']}건, {adj['total_quantity']:,.1f}g)")
|
||||
else:
|
||||
total_adjustment_value += value
|
||||
print(f" {adj_type}: +₩{value:,.0f} ({adj['count']}건, {adj['total_quantity']:,.1f}g)")
|
||||
|
||||
print(f" 순 보정 금액: ₩{total_adjustment_value:,.0f}")
|
||||
print()
|
||||
|
||||
# 5. 예상 재고 자산 계산
|
||||
print("5. 예상 재고 자산 계산")
|
||||
print("-" * 60)
|
||||
|
||||
expected_value = total_purchase - total_dispensed_value + total_adjustment_value
|
||||
|
||||
print(f" 입고 금액: ₩{total_purchase:,.0f}")
|
||||
print(f" - 출고 금액: ₩{total_dispensed_value:,.0f}")
|
||||
print(f" + 보정 금액: ₩{total_adjustment_value:,.0f}")
|
||||
print(f" = 예상 재고 자산: ₩{expected_value:,.0f}")
|
||||
print()
|
||||
|
||||
# 6. 차이 분석
|
||||
print("6. 차이 분석")
|
||||
print("-" * 60)
|
||||
|
||||
discrepancy = system_total - expected_value
|
||||
discrepancy_pct = (discrepancy / expected_value * 100) if expected_value != 0 else 0
|
||||
|
||||
print(f" 시스템 재고 자산: ₩{system_total:,.0f}")
|
||||
print(f" 예상 재고 자산: ₩{expected_value:,.0f}")
|
||||
print(f" 차이: ₩{discrepancy:,.0f} ({discrepancy_pct:+.2f}%)")
|
||||
print()
|
||||
|
||||
# 7. 상세 불일치 원인 분석
|
||||
print("7. 잠재적 불일치 원인 분석")
|
||||
print("-" * 60)
|
||||
|
||||
# 7-1. LOT과 입고장 매칭 확인
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) as unmatched_lots
|
||||
FROM inventory_lots il
|
||||
WHERE il.receipt_id IS NULL AND il.is_depleted = 0
|
||||
""")
|
||||
unmatched = cursor.fetchone()
|
||||
|
||||
if unmatched['unmatched_lots'] > 0:
|
||||
print(f" ⚠️ 입고장과 매칭되지 않은 LOT: {unmatched['unmatched_lots']}개")
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
herb_name,
|
||||
lot_number,
|
||||
quantity_onhand,
|
||||
unit_price_per_g,
|
||||
quantity_onhand * unit_price_per_g as value
|
||||
FROM inventory_lots il
|
||||
JOIN herb_items h ON il.herb_item_id = h.herb_item_id
|
||||
WHERE il.receipt_id IS NULL AND il.is_depleted = 0
|
||||
ORDER BY value DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
|
||||
unmatched_lots = cursor.fetchall()
|
||||
for lot in unmatched_lots:
|
||||
print(f" - {lot['herb_name']} (LOT: {lot['lot_number']}): ₩{lot['value']:,.0f}")
|
||||
|
||||
# 7-2. 단가 변동 확인
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
h.herb_name,
|
||||
MIN(il.unit_price_per_g) as min_price,
|
||||
MAX(il.unit_price_per_g) as max_price,
|
||||
AVG(il.unit_price_per_g) as avg_price,
|
||||
MAX(il.unit_price_per_g) - MIN(il.unit_price_per_g) as price_diff
|
||||
FROM inventory_lots il
|
||||
JOIN herb_items h ON il.herb_item_id = h.herb_item_id
|
||||
WHERE il.is_depleted = 0 AND il.quantity_onhand > 0
|
||||
GROUP BY h.herb_item_id, h.herb_name
|
||||
HAVING price_diff > 0
|
||||
ORDER BY price_diff DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
|
||||
price_variations = cursor.fetchall()
|
||||
if price_variations:
|
||||
print(f"\n ⚠️ 단가 변동이 큰 약재 (동일 약재 다른 단가):")
|
||||
for item in price_variations:
|
||||
print(f" - {item['herb_name']}: ₩{item['min_price']:.2f} ~ ₩{item['max_price']:.2f} (차이: ₩{item['price_diff']:.2f})")
|
||||
|
||||
# 7-3. 입고장 없는 출고 확인
|
||||
cursor.execute("""
|
||||
SELECT COUNT(DISTINCT pd.lot_id) as orphan_dispenses
|
||||
FROM prescription_details pd
|
||||
LEFT JOIN inventory_lots il ON pd.lot_id = il.lot_id
|
||||
WHERE il.lot_id IS NULL
|
||||
""")
|
||||
orphan = cursor.fetchone()
|
||||
|
||||
if orphan['orphan_dispenses'] > 0:
|
||||
print(f"\n ⚠️ LOT 정보 없는 출고: {orphan['orphan_dispenses']}건")
|
||||
|
||||
# 7-4. 음수 재고 확인
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) as negative_stock
|
||||
FROM inventory_lots
|
||||
WHERE quantity_onhand < 0
|
||||
""")
|
||||
negative = cursor.fetchone()
|
||||
|
||||
if negative['negative_stock'] > 0:
|
||||
print(f"\n ⚠️ 음수 재고 LOT: {negative['negative_stock']}개")
|
||||
|
||||
# 8. 권장사항
|
||||
print("\n8. 권장사항")
|
||||
print("-" * 60)
|
||||
|
||||
if abs(discrepancy) > 1000:
|
||||
print(" 🔴 상당한 금액 차이가 발생했습니다. 다음 사항을 확인하세요:")
|
||||
print(" 1) 모든 입고장이 inventory_lots에 정확히 반영되었는지 확인")
|
||||
print(" 2) 출고 시 올바른 LOT과 단가가 적용되었는지 확인")
|
||||
print(" 3) 재고 보정 내역이 정확히 기록되었는지 확인")
|
||||
print(" 4) 초기 재고 입력 시 단가가 정확했는지 확인")
|
||||
|
||||
if unmatched['unmatched_lots'] > 0:
|
||||
print(f" 5) 입고장과 매칭되지 않은 {unmatched['unmatched_lots']}개 LOT 확인 필요")
|
||||
else:
|
||||
print(" ✅ 재고 자산이 대체로 일치합니다.")
|
||||
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyze_inventory_discrepancy()
|
||||
315
dev_scripts/analyze_inventory_full.py
Normal file
315
dev_scripts/analyze_inventory_full.py
Normal file
@ -0,0 +1,315 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
재고 자산 금액 불일치 상세 분석
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
from decimal import Decimal, getcontext
|
||||
|
||||
# Decimal 정밀도 설정
|
||||
getcontext().prec = 10
|
||||
|
||||
def analyze_inventory_discrepancy():
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
print("=" * 80)
|
||||
print("재고 자산 금액 불일치 상세 분석")
|
||||
print("분석 시간:", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
# 1. 현재 inventory_lots 기준 재고 자산
|
||||
print("1. 현재 시스템 재고 자산 (inventory_lots 테이블)")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
SUM(quantity_onhand * unit_price_per_g) as total_value,
|
||||
COUNT(*) as lot_count,
|
||||
SUM(quantity_onhand) as total_quantity,
|
||||
COUNT(DISTINCT herb_item_id) as herb_count
|
||||
FROM inventory_lots
|
||||
WHERE is_depleted = 0 AND quantity_onhand > 0
|
||||
""")
|
||||
|
||||
result = cursor.fetchone()
|
||||
system_total = result['total_value'] or 0
|
||||
|
||||
print(f" 💰 총 재고 자산: ₩{system_total:,.0f}")
|
||||
print(f" 📦 활성 LOT 수: {result['lot_count']}개")
|
||||
print(f" ⚖️ 총 재고량: {result['total_quantity']:,.1f}g")
|
||||
print(f" 🌿 약재 종류: {result['herb_count']}종")
|
||||
print()
|
||||
|
||||
# 2. 입고장 기준 분석
|
||||
print("2. 입고장 데이터 분석 (purchase_receipts + purchase_receipt_lines)")
|
||||
print("-" * 60)
|
||||
|
||||
# 전체 입고 금액 (purchase_receipt_lines 기준)
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
SUM(prl.line_total) as total_purchase,
|
||||
COUNT(DISTINCT pr.receipt_id) as receipt_count,
|
||||
COUNT(*) as line_count,
|
||||
SUM(prl.quantity_g) as total_quantity
|
||||
FROM purchase_receipt_lines prl
|
||||
JOIN purchase_receipts pr ON prl.receipt_id = pr.receipt_id
|
||||
""")
|
||||
|
||||
receipts = cursor.fetchone()
|
||||
total_purchase = receipts['total_purchase'] or 0
|
||||
|
||||
print(f" 📋 총 입고 금액: ₩{total_purchase:,.0f}")
|
||||
print(f" 📑 입고장 수: {receipts['receipt_count']}건")
|
||||
print(f" 📝 입고 라인 수: {receipts['line_count']}개")
|
||||
print(f" ⚖️ 총 입고량: {receipts['total_quantity']:,.1f}g")
|
||||
|
||||
# 입고장별 요약도 확인
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
pr.receipt_id,
|
||||
pr.receipt_no,
|
||||
pr.receipt_date,
|
||||
pr.total_amount as receipt_total,
|
||||
SUM(prl.line_total) as lines_sum
|
||||
FROM purchase_receipts pr
|
||||
LEFT JOIN purchase_receipt_lines prl ON pr.receipt_id = prl.receipt_id
|
||||
GROUP BY pr.receipt_id
|
||||
ORDER BY pr.receipt_date DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
|
||||
print("\n 최근 입고장 5건:")
|
||||
recent_receipts = cursor.fetchall()
|
||||
for r in recent_receipts:
|
||||
print(f" - {r['receipt_no']} ({r['receipt_date']}): ₩{r['lines_sum']:,.0f}")
|
||||
|
||||
print()
|
||||
|
||||
# 3. inventory_lots와 purchase_receipt_lines 매칭 분석
|
||||
print("3. LOT-입고장 매칭 분석")
|
||||
print("-" * 60)
|
||||
|
||||
# receipt_line_id로 연결된 LOT 분석
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
COUNT(*) as total_lots,
|
||||
SUM(CASE WHEN receipt_line_id IS NOT NULL THEN 1 ELSE 0 END) as matched_lots,
|
||||
SUM(CASE WHEN receipt_line_id IS NULL THEN 1 ELSE 0 END) as unmatched_lots,
|
||||
SUM(CASE WHEN receipt_line_id IS NOT NULL THEN quantity_onhand * unit_price_per_g ELSE 0 END) as matched_value,
|
||||
SUM(CASE WHEN receipt_line_id IS NULL THEN quantity_onhand * unit_price_per_g ELSE 0 END) as unmatched_value
|
||||
FROM inventory_lots
|
||||
WHERE is_depleted = 0 AND quantity_onhand > 0
|
||||
""")
|
||||
|
||||
matching = cursor.fetchone()
|
||||
|
||||
print(f" ✅ 입고장과 연결된 LOT: {matching['matched_lots']}개 (₩{matching['matched_value']:,.0f})")
|
||||
print(f" ❌ 입고장 없는 LOT: {matching['unmatched_lots']}개 (₩{matching['unmatched_value']:,.0f})")
|
||||
|
||||
if matching['unmatched_lots'] > 0:
|
||||
print("\n 입고장 없는 LOT 상세:")
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
h.herb_name,
|
||||
il.lot_number,
|
||||
il.quantity_onhand,
|
||||
il.unit_price_per_g,
|
||||
il.quantity_onhand * il.unit_price_per_g as value,
|
||||
il.received_date
|
||||
FROM inventory_lots il
|
||||
JOIN herb_items h ON il.herb_item_id = h.herb_item_id
|
||||
WHERE il.receipt_line_id IS NULL
|
||||
AND il.is_depleted = 0
|
||||
AND il.quantity_onhand > 0
|
||||
ORDER BY value DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
|
||||
unmatched_lots = cursor.fetchall()
|
||||
for lot in unmatched_lots:
|
||||
print(f" - {lot['herb_name']} (LOT: {lot['lot_number']})")
|
||||
print(f" 재고: {lot['quantity_onhand']:,.0f}g, 단가: ₩{lot['unit_price_per_g']:.2f}, 금액: ₩{lot['value']:,.0f}")
|
||||
|
||||
print()
|
||||
|
||||
# 4. 입고장 라인과 LOT 비교
|
||||
print("4. 입고장 라인별 LOT 생성 확인")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
COUNT(*) as total_lines,
|
||||
SUM(CASE WHEN il.lot_id IS NOT NULL THEN 1 ELSE 0 END) as lines_with_lot,
|
||||
SUM(CASE WHEN il.lot_id IS NULL THEN 1 ELSE 0 END) as lines_without_lot
|
||||
FROM purchase_receipt_lines prl
|
||||
LEFT JOIN inventory_lots il ON prl.line_id = il.receipt_line_id
|
||||
""")
|
||||
|
||||
line_matching = cursor.fetchone()
|
||||
|
||||
print(f" 📝 전체 입고 라인: {line_matching['total_lines']}개")
|
||||
print(f" ✅ LOT 생성된 라인: {line_matching['lines_with_lot']}개")
|
||||
print(f" ❌ LOT 없는 라인: {line_matching['lines_without_lot']}개")
|
||||
|
||||
if line_matching['lines_without_lot'] > 0:
|
||||
print("\n ⚠️ LOT이 생성되지 않은 입고 라인이 있습니다!")
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
pr.receipt_no,
|
||||
pr.receipt_date,
|
||||
h.herb_name,
|
||||
prl.quantity_g,
|
||||
prl.line_total
|
||||
FROM purchase_receipt_lines prl
|
||||
JOIN purchase_receipts pr ON prl.receipt_id = pr.receipt_id
|
||||
JOIN herb_items h ON prl.herb_item_id = h.herb_item_id
|
||||
LEFT JOIN inventory_lots il ON prl.line_id = il.receipt_line_id
|
||||
WHERE il.lot_id IS NULL
|
||||
ORDER BY prl.line_total DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
|
||||
missing_lots = cursor.fetchall()
|
||||
for line in missing_lots:
|
||||
print(f" - {line['receipt_no']} ({line['receipt_date']}): {line['herb_name']}")
|
||||
print(f" 수량: {line['quantity_g']:,.0f}g, 금액: ₩{line['line_total']:,.0f}")
|
||||
|
||||
print()
|
||||
|
||||
# 5. 금액 차이 계산
|
||||
print("5. 재고 자산 차이 분석")
|
||||
print("-" * 60)
|
||||
|
||||
# 입고장 라인별로 생성된 LOT의 현재 재고 가치 합계
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
SUM(il.quantity_onhand * il.unit_price_per_g) as current_lot_value,
|
||||
SUM(prl.line_total) as original_purchase_value
|
||||
FROM purchase_receipt_lines prl
|
||||
JOIN inventory_lots il ON prl.line_id = il.receipt_line_id
|
||||
WHERE il.is_depleted = 0 AND il.quantity_onhand > 0
|
||||
""")
|
||||
|
||||
value_comparison = cursor.fetchone()
|
||||
|
||||
if value_comparison['current_lot_value']:
|
||||
print(f" 💰 현재 LOT 재고 가치: ₩{value_comparison['current_lot_value']:,.0f}")
|
||||
print(f" 📋 원본 입고 금액: ₩{value_comparison['original_purchase_value']:,.0f}")
|
||||
print(f" 📊 차이: ₩{(value_comparison['current_lot_value'] - value_comparison['original_purchase_value']):,.0f}")
|
||||
|
||||
print()
|
||||
|
||||
# 6. 출고 내역 확인
|
||||
print("6. 출고 및 소비 내역")
|
||||
print("-" * 60)
|
||||
|
||||
# 처방전을 통한 출고가 있는지 확인
|
||||
cursor.execute("""
|
||||
SELECT name FROM sqlite_master
|
||||
WHERE type='table' AND name IN ('prescriptions', 'prescription_details')
|
||||
""")
|
||||
prescription_tables = cursor.fetchall()
|
||||
|
||||
if len(prescription_tables) == 2:
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
SUM(pd.quantity * il.unit_price_per_g) as dispensed_value,
|
||||
SUM(pd.quantity) as dispensed_quantity,
|
||||
COUNT(DISTINCT p.prescription_id) as prescription_count
|
||||
FROM prescription_details pd
|
||||
JOIN prescriptions p ON pd.prescription_id = p.prescription_id
|
||||
JOIN inventory_lots il ON pd.lot_id = il.lot_id
|
||||
WHERE p.status IN ('completed', 'dispensed')
|
||||
""")
|
||||
|
||||
dispensed = cursor.fetchone()
|
||||
if dispensed and dispensed['dispensed_value']:
|
||||
print(f" 💊 처방 출고 금액: ₩{dispensed['dispensed_value']:,.0f}")
|
||||
print(f" ⚖️ 처방 출고량: {dispensed['dispensed_quantity']:,.1f}g")
|
||||
print(f" 📋 처방전 수: {dispensed['prescription_count']}건")
|
||||
else:
|
||||
print(" 처방전 테이블이 없습니다.")
|
||||
|
||||
# 복합제 소비 확인
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
SUM(cc.quantity_used * il.unit_price_per_g) as compound_value,
|
||||
SUM(cc.quantity_used) as compound_quantity,
|
||||
COUNT(DISTINCT cc.compound_id) as compound_count
|
||||
FROM compound_consumptions cc
|
||||
JOIN inventory_lots il ON cc.lot_id = il.lot_id
|
||||
""")
|
||||
|
||||
compounds = cursor.fetchone()
|
||||
if compounds and compounds['compound_value']:
|
||||
print(f" 🏭 복합제 소비 금액: ₩{compounds['compound_value']:,.0f}")
|
||||
print(f" ⚖️ 복합제 소비량: {compounds['compound_quantity']:,.1f}g")
|
||||
print(f" 📦 복합제 수: {compounds['compound_count']}개")
|
||||
|
||||
print()
|
||||
|
||||
# 7. 재고 보정 내역
|
||||
print("7. 재고 보정 내역")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
adjustment_type,
|
||||
SUM(quantity) as total_quantity,
|
||||
SUM(quantity * unit_price) as total_value,
|
||||
COUNT(*) as count
|
||||
FROM stock_adjustments
|
||||
GROUP BY adjustment_type
|
||||
""")
|
||||
|
||||
adjustments = cursor.fetchall()
|
||||
total_adjustment = 0
|
||||
|
||||
for adj in adjustments:
|
||||
adj_type = adj['adjustment_type']
|
||||
value = adj['total_value'] or 0
|
||||
|
||||
if adj_type in ['disposal', 'loss', 'decrease']:
|
||||
total_adjustment -= value
|
||||
print(f" ➖ {adj_type}: -₩{value:,.0f} ({adj['count']}건, {adj['total_quantity']:,.1f}g)")
|
||||
else:
|
||||
total_adjustment += value
|
||||
print(f" ➕ {adj_type}: +₩{value:,.0f} ({adj['count']}건, {adj['total_quantity']:,.1f}g)")
|
||||
|
||||
print(f"\n 📊 순 보정 금액: ₩{total_adjustment:,.0f}")
|
||||
print()
|
||||
|
||||
# 8. 최종 분석 결과
|
||||
print("8. 최종 분석 결과")
|
||||
print("=" * 60)
|
||||
|
||||
print(f"\n 💰 화면 표시 재고 자산: ₩5,875,708")
|
||||
print(f" 📊 실제 계산 재고 자산: ₩{system_total:,.0f}")
|
||||
print(f" ❗ 차이: ₩{5875708 - system_total:,.0f}")
|
||||
|
||||
print("\n 🔍 불일치 원인:")
|
||||
|
||||
if matching['unmatched_lots'] > 0:
|
||||
print(f" 1) 입고장과 연결되지 않은 LOT {matching['unmatched_lots']}개 (₩{matching['unmatched_value']:,.0f})")
|
||||
|
||||
if line_matching['lines_without_lot'] > 0:
|
||||
print(f" 2) LOT이 생성되지 않은 입고 라인 {line_matching['lines_without_lot']}개")
|
||||
|
||||
print(f" 3) 화면의 ₩5,875,708과 실제 DB의 ₩{system_total:,.0f} 차이")
|
||||
|
||||
# 화면에 표시되는 금액이 어디서 오는지 추가 확인
|
||||
print("\n 💡 추가 확인 필요사항:")
|
||||
print(" - 프론트엔드에서 재고 자산을 계산하는 로직 확인")
|
||||
print(" - 캐시된 데이터나 별도 계산 로직이 있는지 확인")
|
||||
print(" - inventory_lots_v2 테이블 데이터와 비교 필요")
|
||||
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyze_inventory_discrepancy()
|
||||
186
dev_scripts/analyze_price_difference.py
Normal file
186
dev_scripts/analyze_price_difference.py
Normal file
@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
입고 단가와 LOT 단가 차이 분석
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
|
||||
def analyze_price_difference():
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
print("=" * 80)
|
||||
print("입고 단가와 LOT 단가 차이 상세 분석")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
# 1. 입고 라인과 LOT의 단가 차이 분석
|
||||
print("1. 입고 라인 vs LOT 단가 비교")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
h.herb_name,
|
||||
prl.line_id,
|
||||
prl.quantity_g as purchase_qty,
|
||||
prl.unit_price_per_g as purchase_price,
|
||||
prl.line_total as purchase_total,
|
||||
il.quantity_received as lot_received_qty,
|
||||
il.quantity_onhand as lot_current_qty,
|
||||
il.unit_price_per_g as lot_price,
|
||||
il.quantity_received * il.unit_price_per_g as lot_original_value,
|
||||
il.quantity_onhand * il.unit_price_per_g as lot_current_value,
|
||||
ABS(prl.unit_price_per_g - il.unit_price_per_g) as price_diff,
|
||||
prl.line_total - (il.quantity_received * il.unit_price_per_g) as value_diff
|
||||
FROM purchase_receipt_lines prl
|
||||
JOIN inventory_lots il ON prl.line_id = il.receipt_line_id
|
||||
JOIN herb_items h ON prl.herb_item_id = h.herb_item_id
|
||||
WHERE ABS(prl.unit_price_per_g - il.unit_price_per_g) > 0.01
|
||||
OR ABS(prl.quantity_g - il.quantity_received) > 0.01
|
||||
ORDER BY ABS(value_diff) DESC
|
||||
""")
|
||||
|
||||
diffs = cursor.fetchall()
|
||||
|
||||
if diffs:
|
||||
print(f" ⚠️ 단가 또는 수량이 다른 항목: {len(diffs)}개\n")
|
||||
|
||||
total_value_diff = 0
|
||||
for i, diff in enumerate(diffs[:10], 1):
|
||||
print(f" {i}. {diff['herb_name']}")
|
||||
print(f" 입고: {diff['purchase_qty']:,.0f}g × ₩{diff['purchase_price']:.2f} = ₩{diff['purchase_total']:,.0f}")
|
||||
print(f" LOT: {diff['lot_received_qty']:,.0f}g × ₩{diff['lot_price']:.2f} = ₩{diff['lot_original_value']:,.0f}")
|
||||
print(f" 차이: ₩{diff['value_diff']:,.0f}")
|
||||
total_value_diff += diff['value_diff']
|
||||
print()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT SUM(prl.line_total - (il.quantity_received * il.unit_price_per_g)) as total_diff
|
||||
FROM purchase_receipt_lines prl
|
||||
JOIN inventory_lots il ON prl.line_id = il.receipt_line_id
|
||||
""")
|
||||
total_diff = cursor.fetchone()['total_diff'] or 0
|
||||
|
||||
print(f" 총 차이 금액: ₩{total_diff:,.0f}")
|
||||
else:
|
||||
print(" ✅ 모든 입고 라인과 LOT의 단가/수량이 일치합니다.")
|
||||
|
||||
# 2. 입고 총액과 LOT 생성 총액 비교
|
||||
print("\n2. 입고 총액 vs LOT 생성 총액")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
SUM(prl.line_total) as purchase_total,
|
||||
SUM(il.quantity_received * il.unit_price_per_g) as lot_creation_total
|
||||
FROM purchase_receipt_lines prl
|
||||
JOIN inventory_lots il ON prl.line_id = il.receipt_line_id
|
||||
""")
|
||||
|
||||
totals = cursor.fetchone()
|
||||
|
||||
print(f" 입고장 총액: ₩{totals['purchase_total']:,.0f}")
|
||||
print(f" LOT 생성 총액: ₩{totals['lot_creation_total']:,.0f}")
|
||||
print(f" 차이: ₩{totals['purchase_total'] - totals['lot_creation_total']:,.0f}")
|
||||
|
||||
# 3. 소비로 인한 차이 분석
|
||||
print("\n3. 소비 내역 상세 분석")
|
||||
print("-" * 60)
|
||||
|
||||
# 복합제 소비 상세
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
c.compound_name,
|
||||
h.herb_name,
|
||||
cc.quantity_used,
|
||||
il.unit_price_per_g,
|
||||
cc.quantity_used * il.unit_price_per_g as consumption_value,
|
||||
cc.consumption_date
|
||||
FROM compound_consumptions cc
|
||||
JOIN inventory_lots il ON cc.lot_id = il.lot_id
|
||||
JOIN compounds c ON cc.compound_id = c.compound_id
|
||||
JOIN herb_items h ON il.herb_item_id = h.herb_item_id
|
||||
ORDER BY consumption_value DESC
|
||||
LIMIT 10
|
||||
""")
|
||||
|
||||
consumptions = cursor.fetchall()
|
||||
|
||||
print(" 복합제 소비 내역 (상위 10개):")
|
||||
total_consumption = 0
|
||||
for cons in consumptions:
|
||||
print(f" - {cons['compound_name']} - {cons['herb_name']}")
|
||||
print(f" {cons['quantity_used']:,.0f}g × ₩{cons['unit_price_per_g']:.2f} = ₩{cons['consumption_value']:,.0f}")
|
||||
total_consumption += cons['consumption_value']
|
||||
|
||||
cursor.execute("""
|
||||
SELECT SUM(cc.quantity_used * il.unit_price_per_g) as total
|
||||
FROM compound_consumptions cc
|
||||
JOIN inventory_lots il ON cc.lot_id = il.lot_id
|
||||
""")
|
||||
total_consumed = cursor.fetchone()['total'] or 0
|
||||
|
||||
print(f"\n 총 소비 금액: ₩{total_consumed:,.0f}")
|
||||
|
||||
# 4. 재고 자산 흐름 요약
|
||||
print("\n4. 재고 자산 흐름 요약")
|
||||
print("=" * 60)
|
||||
|
||||
# 입고장 기준
|
||||
cursor.execute("SELECT SUM(line_total) as total FROM purchase_receipt_lines")
|
||||
receipt_total = cursor.fetchone()['total'] or 0
|
||||
|
||||
# LOT 생성 기준
|
||||
cursor.execute("""
|
||||
SELECT SUM(quantity_received * unit_price_per_g) as total
|
||||
FROM inventory_lots
|
||||
WHERE receipt_line_id IS NOT NULL
|
||||
""")
|
||||
lot_creation = cursor.fetchone()['total'] or 0
|
||||
|
||||
# 현재 LOT 재고
|
||||
cursor.execute("""
|
||||
SELECT SUM(quantity_onhand * unit_price_per_g) as total
|
||||
FROM inventory_lots
|
||||
WHERE is_depleted = 0 AND quantity_onhand > 0
|
||||
""")
|
||||
current_inventory = cursor.fetchone()['total'] or 0
|
||||
|
||||
print(f" 1) 입고장 총액: ₩{receipt_total:,.0f}")
|
||||
print(f" 2) LOT 생성 총액: ₩{lot_creation:,.0f}")
|
||||
print(f" 차이 (1-2): ₩{receipt_total - lot_creation:,.0f}")
|
||||
print()
|
||||
print(f" 3) 복합제 소비: ₩{total_consumed:,.0f}")
|
||||
print(f" 4) 현재 재고: ₩{current_inventory:,.0f}")
|
||||
print()
|
||||
print(f" 예상 재고 (2-3): ₩{lot_creation - total_consumed:,.0f}")
|
||||
print(f" 실제 재고: ₩{current_inventory:,.0f}")
|
||||
print(f" 차이: ₩{current_inventory - (lot_creation - total_consumed):,.0f}")
|
||||
|
||||
# 5. 차이 원인 설명
|
||||
print("\n5. 차이 원인 분석")
|
||||
print("-" * 60)
|
||||
|
||||
price_diff = receipt_total - lot_creation
|
||||
if abs(price_diff) > 1000:
|
||||
print(f"\n 💡 입고장과 LOT 생성 시 ₩{abs(price_diff):,.0f} 차이가 있습니다.")
|
||||
print(" 가능한 원인:")
|
||||
print(" - VAT 포함/제외 계산 차이")
|
||||
print(" - 단가 반올림 차이")
|
||||
print(" - 입고 시점의 환율 적용 차이")
|
||||
|
||||
consumption_diff = current_inventory - (lot_creation - total_consumed)
|
||||
if abs(consumption_diff) > 1000:
|
||||
print(f"\n 💡 예상 재고와 실제 재고 간 ₩{abs(consumption_diff):,.0f} 차이가 있습니다.")
|
||||
print(" 가능한 원인:")
|
||||
print(" - 재고 보정 내역")
|
||||
print(" - 소비 시 반올림 오차 누적")
|
||||
print(" - 초기 데이터 입력 오류")
|
||||
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyze_price_difference()
|
||||
45
dev_scripts/check_herb_data.py
Normal file
45
dev_scripts/check_herb_data.py
Normal file
@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
"""약재 데이터 확인"""
|
||||
|
||||
import sqlite3
|
||||
|
||||
conn = sqlite3.connect('kdrug.db')
|
||||
cur = conn.cursor()
|
||||
|
||||
# 확장 정보가 있는 약재 확인
|
||||
cur.execute("""
|
||||
SELECT COUNT(*) FROM herb_master_extended
|
||||
WHERE nature IS NOT NULL OR taste IS NOT NULL
|
||||
""")
|
||||
extended_count = cur.fetchone()[0]
|
||||
print(f"확장 정보가 있는 약재: {extended_count}개")
|
||||
|
||||
# 효능 태그가 있는 약재 확인
|
||||
cur.execute("SELECT COUNT(DISTINCT ingredient_code) FROM herb_item_tags")
|
||||
tagged_count = cur.fetchone()[0]
|
||||
print(f"효능 태그가 있는 약재: {tagged_count}개")
|
||||
|
||||
# 구체적인 데이터 확인
|
||||
cur.execute("""
|
||||
SELECT hme.ingredient_code, hme.herb_name, hme.nature, hme.taste
|
||||
FROM herb_master_extended hme
|
||||
WHERE hme.nature IS NOT NULL OR hme.taste IS NOT NULL
|
||||
LIMIT 5
|
||||
""")
|
||||
print("\n확장 정보 샘플:")
|
||||
for row in cur.fetchall():
|
||||
print(f" - {row[1]} ({row[0]}): {row[2]}/{row[3]}")
|
||||
|
||||
# herb_item_tags 데이터 확인
|
||||
cur.execute("""
|
||||
SELECT hit.ingredient_code, het.name, COUNT(*) as count
|
||||
FROM herb_item_tags hit
|
||||
JOIN herb_efficacy_tags het ON hit.tag_id = het.tag_id
|
||||
GROUP BY hit.ingredient_code
|
||||
LIMIT 5
|
||||
""")
|
||||
print("\n효능 태그 샘플:")
|
||||
for row in cur.fetchall():
|
||||
print(f" - {row[0]}: {row[2]}개 태그")
|
||||
|
||||
conn.close()
|
||||
143
dev_scripts/check_lot_creation_methods.py
Normal file
143
dev_scripts/check_lot_creation_methods.py
Normal file
@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
LOT 생성 방법 분석 - 입고장 연결 vs 독립 생성
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
|
||||
def check_lot_creation_methods():
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
print("=" * 80)
|
||||
print("📦 LOT 생성 방법 분석")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
# 1. 전체 LOT 현황
|
||||
print("1. 전체 LOT 현황")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
COUNT(*) as total_lots,
|
||||
SUM(CASE WHEN receipt_line_id IS NOT NULL THEN 1 ELSE 0 END) as with_receipt,
|
||||
SUM(CASE WHEN receipt_line_id IS NULL THEN 1 ELSE 0 END) as without_receipt,
|
||||
SUM(CASE WHEN is_depleted = 0 THEN 1 ELSE 0 END) as active_lots
|
||||
FROM inventory_lots
|
||||
""")
|
||||
|
||||
stats = cursor.fetchone()
|
||||
|
||||
print(f" 전체 LOT 수: {stats['total_lots']}개")
|
||||
print(f" ✅ 입고장 연결: {stats['with_receipt']}개")
|
||||
print(f" ❌ 입고장 없음: {stats['without_receipt']}개")
|
||||
print(f" 활성 LOT: {stats['active_lots']}개")
|
||||
|
||||
# 2. 입고장 없는 LOT 상세
|
||||
if stats['without_receipt'] > 0:
|
||||
print("\n2. 입고장 없이 생성된 LOT 상세")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
il.lot_id,
|
||||
h.herb_name,
|
||||
il.lot_number,
|
||||
il.quantity_received,
|
||||
il.quantity_onhand,
|
||||
il.unit_price_per_g,
|
||||
il.quantity_onhand * il.unit_price_per_g as value,
|
||||
il.received_date,
|
||||
il.created_at
|
||||
FROM inventory_lots il
|
||||
JOIN herb_items h ON il.herb_item_id = h.herb_item_id
|
||||
WHERE il.receipt_line_id IS NULL
|
||||
ORDER BY il.created_at DESC
|
||||
""")
|
||||
|
||||
no_receipt_lots = cursor.fetchall()
|
||||
|
||||
for lot in no_receipt_lots:
|
||||
print(f"\n LOT {lot['lot_id']}: {lot['herb_name']}")
|
||||
print(f" LOT 번호: {lot['lot_number'] or 'None'}")
|
||||
print(f" 수량: {lot['quantity_received']:,.0f}g → {lot['quantity_onhand']:,.0f}g")
|
||||
print(f" 단가: ₩{lot['unit_price_per_g']:.2f}")
|
||||
print(f" 재고 가치: ₩{lot['value']:,.0f}")
|
||||
print(f" 입고일: {lot['received_date']}")
|
||||
print(f" 생성일: {lot['created_at']}")
|
||||
|
||||
# 금액 합계
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
SUM(quantity_onhand * unit_price_per_g) as total_value,
|
||||
SUM(quantity_onhand) as total_qty
|
||||
FROM inventory_lots
|
||||
WHERE receipt_line_id IS NULL
|
||||
AND is_depleted = 0
|
||||
AND quantity_onhand > 0
|
||||
""")
|
||||
|
||||
no_receipt_total = cursor.fetchone()
|
||||
|
||||
if no_receipt_total['total_value']:
|
||||
print(f"\n 📊 입고장 없는 LOT 합계:")
|
||||
print(f" 총 재고량: {no_receipt_total['total_qty']:,.0f}g")
|
||||
print(f" 총 재고 가치: ₩{no_receipt_total['total_value']:,.0f}")
|
||||
|
||||
# 3. LOT 생성 방법별 재고 자산
|
||||
print("\n3. LOT 생성 방법별 재고 자산")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
CASE
|
||||
WHEN receipt_line_id IS NOT NULL THEN '입고장 연결'
|
||||
ELSE '직접 생성'
|
||||
END as creation_type,
|
||||
COUNT(*) as lot_count,
|
||||
SUM(quantity_onhand) as total_qty,
|
||||
SUM(quantity_onhand * unit_price_per_g) as total_value
|
||||
FROM inventory_lots
|
||||
WHERE is_depleted = 0 AND quantity_onhand > 0
|
||||
GROUP BY creation_type
|
||||
""")
|
||||
|
||||
by_type = cursor.fetchall()
|
||||
|
||||
total_value = 0
|
||||
for row in by_type:
|
||||
print(f"\n {row['creation_type']}:")
|
||||
print(f" LOT 수: {row['lot_count']}개")
|
||||
print(f" 재고량: {row['total_qty']:,.0f}g")
|
||||
print(f" 재고 가치: ₩{row['total_value']:,.0f}")
|
||||
total_value += row['total_value']
|
||||
|
||||
print(f"\n 📊 전체 재고 자산: ₩{total_value:,.0f}")
|
||||
|
||||
# 4. 시스템 설계 분석
|
||||
print("\n4. 시스템 설계 분석")
|
||||
print("=" * 60)
|
||||
|
||||
print("\n 💡 현재 시스템은 두 가지 방법으로 LOT 생성 가능:")
|
||||
print(" 1) 입고장 등록 시 자동 생성 (receipt_line_id 연결)")
|
||||
print(" 2) 재고 직접 입력 (receipt_line_id = NULL)")
|
||||
print()
|
||||
print(" 📌 재고 자산 계산 로직:")
|
||||
print(" - 입고장 연결 여부와 관계없이")
|
||||
print(" - 모든 활성 LOT의 (수량 × 단가) 합계")
|
||||
print()
|
||||
|
||||
if stats['without_receipt'] > 0:
|
||||
print(" ⚠️ 주의사항:")
|
||||
print(" - 입고장 없는 LOT이 존재합니다")
|
||||
print(" - 초기 재고 입력이나 재고 조정으로 생성된 것으로 추정")
|
||||
print(" - 회계 추적을 위해서는 입고장 연결 권장")
|
||||
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_lot_creation_methods()
|
||||
179
dev_scripts/check_missing_lots.py
Normal file
179
dev_scripts/check_missing_lots.py
Normal file
@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
LOT이 생성되지 않은 입고 라인 확인
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
|
||||
def check_missing_lots():
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
print("=" * 80)
|
||||
print("LOT이 생성되지 않은 입고 라인 분석")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
# 1. 전체 입고 라인과 LOT 매칭 상태
|
||||
print("1. 입고 라인 - LOT 매칭 현황")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
COUNT(*) as total_lines,
|
||||
SUM(CASE WHEN il.lot_id IS NOT NULL THEN 1 ELSE 0 END) as lines_with_lot,
|
||||
SUM(CASE WHEN il.lot_id IS NULL THEN 1 ELSE 0 END) as lines_without_lot,
|
||||
SUM(prl.line_total) as total_purchase_amount,
|
||||
SUM(CASE WHEN il.lot_id IS NOT NULL THEN prl.line_total ELSE 0 END) as amount_with_lot,
|
||||
SUM(CASE WHEN il.lot_id IS NULL THEN prl.line_total ELSE 0 END) as amount_without_lot
|
||||
FROM purchase_receipt_lines prl
|
||||
LEFT JOIN inventory_lots il ON prl.line_id = il.receipt_line_id
|
||||
""")
|
||||
|
||||
result = cursor.fetchone()
|
||||
|
||||
print(f" 총 입고 라인: {result['total_lines']}개")
|
||||
print(f" ✅ LOT 생성됨: {result['lines_with_lot']}개 (₩{result['amount_with_lot']:,.0f})")
|
||||
print(f" ❌ LOT 없음: {result['lines_without_lot']}개 (₩{result['amount_without_lot']:,.0f})")
|
||||
print()
|
||||
print(f" 총 입고 금액: ₩{result['total_purchase_amount']:,.0f}")
|
||||
print(f" LOT 없는 금액: ₩{result['amount_without_lot']:,.0f}")
|
||||
|
||||
if result['amount_without_lot'] > 0:
|
||||
print(f"\n ⚠️ LOT이 생성되지 않은 입고 금액이 ₩{result['amount_without_lot']:,.0f} 있습니다!")
|
||||
print(" 이것이 DB 재고와 예상 재고 차이(₩55,500)의 원인일 가능성이 높습니다.")
|
||||
|
||||
# 2. LOT이 없는 입고 라인 상세
|
||||
if result['lines_without_lot'] > 0:
|
||||
print("\n2. LOT이 생성되지 않은 입고 라인 상세")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
pr.receipt_no,
|
||||
pr.receipt_date,
|
||||
h.herb_name,
|
||||
prl.quantity_g,
|
||||
prl.unit_price_per_g,
|
||||
prl.line_total,
|
||||
prl.lot_number,
|
||||
prl.line_id
|
||||
FROM purchase_receipt_lines prl
|
||||
JOIN purchase_receipts pr ON prl.receipt_id = pr.receipt_id
|
||||
JOIN herb_items h ON prl.herb_item_id = h.herb_item_id
|
||||
LEFT JOIN inventory_lots il ON prl.line_id = il.receipt_line_id
|
||||
WHERE il.lot_id IS NULL
|
||||
ORDER BY prl.line_total DESC
|
||||
""")
|
||||
|
||||
missing_lots = cursor.fetchall()
|
||||
|
||||
total_missing_amount = 0
|
||||
print("\n LOT이 생성되지 않은 입고 라인:")
|
||||
for i, line in enumerate(missing_lots, 1):
|
||||
print(f"\n {i}. {line['herb_name']}")
|
||||
print(f" 입고장: {line['receipt_no']} ({line['receipt_date']})")
|
||||
print(f" 수량: {line['quantity_g']:,.0f}g")
|
||||
print(f" 단가: ₩{line['unit_price_per_g']:.2f}/g")
|
||||
print(f" 금액: ₩{line['line_total']:,.0f}")
|
||||
print(f" LOT번호: {line['lot_number'] or 'None'}")
|
||||
print(f" Line ID: {line['line_id']}")
|
||||
total_missing_amount += line['line_total']
|
||||
|
||||
print(f"\n 총 누락 금액: ₩{total_missing_amount:,.0f}")
|
||||
|
||||
# 3. 반대로 입고 라인 없는 LOT 확인
|
||||
print("\n3. 입고 라인과 연결되지 않은 LOT")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
COUNT(*) as orphan_lots,
|
||||
SUM(quantity_onhand * unit_price_per_g) as orphan_value,
|
||||
SUM(quantity_onhand) as orphan_quantity
|
||||
FROM inventory_lots
|
||||
WHERE receipt_line_id IS NULL
|
||||
AND is_depleted = 0
|
||||
AND quantity_onhand > 0
|
||||
""")
|
||||
|
||||
orphans = cursor.fetchone()
|
||||
|
||||
if orphans['orphan_lots'] > 0:
|
||||
print(f" 입고 라인 없는 LOT: {orphans['orphan_lots']}개")
|
||||
print(f" 해당 재고 가치: ₩{orphans['orphan_value']:,.0f}")
|
||||
print(f" 해당 재고량: {orphans['orphan_quantity']:,.0f}g")
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
h.herb_name,
|
||||
il.lot_number,
|
||||
il.quantity_onhand,
|
||||
il.unit_price_per_g,
|
||||
il.quantity_onhand * il.unit_price_per_g as value,
|
||||
il.received_date
|
||||
FROM inventory_lots il
|
||||
JOIN herb_items h ON il.herb_item_id = h.herb_item_id
|
||||
WHERE il.receipt_line_id IS NULL
|
||||
AND il.is_depleted = 0
|
||||
AND il.quantity_onhand > 0
|
||||
ORDER BY value DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
|
||||
orphan_lots = cursor.fetchall()
|
||||
if orphan_lots:
|
||||
print("\n 상위 5개 입고 라인 없는 LOT:")
|
||||
for lot in orphan_lots:
|
||||
print(f" - {lot['herb_name']} (LOT: {lot['lot_number']})")
|
||||
print(f" 재고: {lot['quantity_onhand']:,.0f}g, 금액: ₩{lot['value']:,.0f}")
|
||||
else:
|
||||
print(" ✅ 모든 LOT이 입고 라인과 연결되어 있습니다.")
|
||||
|
||||
# 4. 금액 차이 분석
|
||||
print("\n4. 금액 차이 최종 분석")
|
||||
print("=" * 60)
|
||||
|
||||
# 현재 DB 재고
|
||||
cursor.execute("""
|
||||
SELECT SUM(quantity_onhand * unit_price_per_g) as total
|
||||
FROM inventory_lots
|
||||
WHERE is_depleted = 0 AND quantity_onhand > 0
|
||||
""")
|
||||
db_total = cursor.fetchone()['total'] or 0
|
||||
|
||||
# 총 입고 - 소비
|
||||
cursor.execute("SELECT SUM(line_total) as total FROM purchase_receipt_lines")
|
||||
total_in = cursor.fetchone()['total'] or 0
|
||||
|
||||
cursor.execute("""
|
||||
SELECT SUM(cc.quantity_used * il.unit_price_per_g) as total
|
||||
FROM compound_consumptions cc
|
||||
JOIN inventory_lots il ON cc.lot_id = il.lot_id
|
||||
""")
|
||||
total_out = cursor.fetchone()['total'] or 0
|
||||
|
||||
expected = total_in - total_out
|
||||
|
||||
print(f" DB 재고 자산: ₩{db_total:,.0f}")
|
||||
print(f" 예상 재고 (입고-소비): ₩{expected:,.0f}")
|
||||
print(f" 차이: ₩{expected - db_total:,.0f}")
|
||||
print()
|
||||
|
||||
if result['amount_without_lot'] > 0:
|
||||
print(f" 💡 LOT 없는 입고 금액: ₩{result['amount_without_lot']:,.0f}")
|
||||
adjusted_expected = (total_in - result['amount_without_lot']) - total_out
|
||||
print(f" 📊 조정된 예상 재고: ₩{adjusted_expected:,.0f}")
|
||||
print(f" 조정 후 차이: ₩{adjusted_expected - db_total:,.0f}")
|
||||
|
||||
if abs(adjusted_expected - db_total) < 1000:
|
||||
print("\n ✅ LOT이 생성되지 않은 입고 라인을 제외하면 차이가 거의 없습니다!")
|
||||
print(" 이것이 차이의 주요 원인입니다.")
|
||||
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_missing_lots()
|
||||
51
dev_scripts/check_purchase_structure.py
Normal file
51
dev_scripts/check_purchase_structure.py
Normal file
@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sqlite3
|
||||
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
print("=== purchase_receipts 테이블 구조 ===")
|
||||
cursor.execute("PRAGMA table_info(purchase_receipts)")
|
||||
columns = cursor.fetchall()
|
||||
for col in columns:
|
||||
print(f" {col[1]}: {col[2]}")
|
||||
|
||||
print("\n=== purchase_receipt_lines 테이블 구조 ===")
|
||||
cursor.execute("PRAGMA table_info(purchase_receipt_lines)")
|
||||
columns = cursor.fetchall()
|
||||
for col in columns:
|
||||
print(f" {col[1]}: {col[2]}")
|
||||
|
||||
print("\n=== 입고장 데이터 샘플 ===")
|
||||
cursor.execute("""
|
||||
SELECT pr.receipt_id, pr.receipt_number, pr.receipt_date,
|
||||
COUNT(prl.line_id) as line_count,
|
||||
SUM(prl.quantity_g) as total_quantity,
|
||||
SUM(prl.total_price) as total_amount
|
||||
FROM purchase_receipts pr
|
||||
LEFT JOIN purchase_receipt_lines prl ON pr.receipt_id = prl.receipt_id
|
||||
GROUP BY pr.receipt_id
|
||||
LIMIT 5
|
||||
""")
|
||||
rows = cursor.fetchall()
|
||||
for row in rows:
|
||||
print(f" 입고장 {row[0]}: {row[1]} ({row[2]})")
|
||||
print(f" - 항목수: {row[3]}개, 총량: {row[4]}g, 총액: ₩{row[5]:,.0f}")
|
||||
|
||||
print("\n=== inventory_lots의 receipt_line_id 연결 확인 ===")
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
COUNT(*) as total_lots,
|
||||
SUM(CASE WHEN receipt_line_id IS NOT NULL THEN 1 ELSE 0 END) as matched_lots,
|
||||
SUM(CASE WHEN receipt_line_id IS NULL THEN 1 ELSE 0 END) as unmatched_lots
|
||||
FROM inventory_lots
|
||||
WHERE is_depleted = 0
|
||||
""")
|
||||
result = cursor.fetchone()
|
||||
print(f" 전체 LOT: {result[0]}개")
|
||||
print(f" 입고장 연결된 LOT: {result[1]}개")
|
||||
print(f" 입고장 연결 안된 LOT: {result[2]}개")
|
||||
|
||||
conn.close()
|
||||
35
dev_scripts/check_purchase_tables.py
Normal file
35
dev_scripts/check_purchase_tables.py
Normal file
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sqlite3
|
||||
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 테이블 목록 확인
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
|
||||
tables = cursor.fetchall()
|
||||
|
||||
print("=== 전체 테이블 목록 ===")
|
||||
for table in tables:
|
||||
print(f" - {table[0]}")
|
||||
|
||||
print("\n=== inventory_lots 테이블 구조 ===")
|
||||
cursor.execute("PRAGMA table_info(inventory_lots)")
|
||||
columns = cursor.fetchall()
|
||||
for col in columns:
|
||||
print(f" {col[1]}: {col[2]}")
|
||||
|
||||
print("\n=== inventory_lots 샘플 데이터 ===")
|
||||
cursor.execute("""
|
||||
SELECT lot_id, lot_number, herb_item_id, quantity_onhand,
|
||||
unit_price_per_g, received_date, receipt_id
|
||||
FROM inventory_lots
|
||||
WHERE is_depleted = 0
|
||||
LIMIT 5
|
||||
""")
|
||||
rows = cursor.fetchall()
|
||||
for row in rows:
|
||||
print(f" LOT {row[0]}: {row[1]}, 재고:{row[3]}g, 단가:₩{row[4]}, 입고일:{row[5]}, receipt_id:{row[6]}")
|
||||
|
||||
conn.close()
|
||||
19
dev_scripts/check_tables.py
Normal file
19
dev_scripts/check_tables.py
Normal file
@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
import sqlite3
|
||||
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
cur = conn.cursor()
|
||||
|
||||
# herb_item_tags 테이블 구조 확인
|
||||
cur.execute("PRAGMA table_info(herb_item_tags)")
|
||||
print("herb_item_tags 테이블 구조:")
|
||||
for row in cur.fetchall():
|
||||
print(f" {row}")
|
||||
|
||||
# 실제 테이블 목록 확인
|
||||
cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'herb%' ORDER BY name")
|
||||
print("\n약재 관련 테이블:")
|
||||
for row in cur.fetchall():
|
||||
print(f" - {row[0]}")
|
||||
|
||||
conn.close()
|
||||
115
dev_scripts/debug_api_calculation.py
Normal file
115
dev_scripts/debug_api_calculation.py
Normal file
@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
API 재고 계산 디버깅
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
|
||||
def debug_api_calculation():
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
print("=" * 80)
|
||||
print("API 재고 계산 디버깅")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
# API와 동일한 쿼리 실행
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
h.herb_item_id,
|
||||
h.insurance_code,
|
||||
h.herb_name,
|
||||
COALESCE(SUM(il.quantity_onhand), 0) as total_quantity,
|
||||
COUNT(DISTINCT il.lot_id) as lot_count,
|
||||
COUNT(DISTINCT il.origin_country) as origin_count,
|
||||
AVG(il.unit_price_per_g) as avg_price,
|
||||
MIN(il.unit_price_per_g) as min_price,
|
||||
MAX(il.unit_price_per_g) as max_price,
|
||||
COALESCE(SUM(il.quantity_onhand * il.unit_price_per_g), 0) as total_value
|
||||
FROM herb_items h
|
||||
LEFT JOIN inventory_lots il ON h.herb_item_id = il.herb_item_id AND il.is_depleted = 0
|
||||
GROUP BY h.herb_item_id, h.insurance_code, h.herb_name
|
||||
HAVING total_quantity > 0
|
||||
ORDER BY total_value DESC
|
||||
""")
|
||||
|
||||
items = cursor.fetchall()
|
||||
|
||||
print("상위 10개 약재별 재고 가치:")
|
||||
print("-" * 60)
|
||||
|
||||
total_api_value = 0
|
||||
for i, item in enumerate(items[:10], 1):
|
||||
value = item['total_value']
|
||||
total_api_value += value
|
||||
print(f"{i:2}. {item['herb_name']:15} 재고:{item['total_quantity']:8.0f}g 금액:₩{value:10,.0f}")
|
||||
|
||||
# 전체 합계 계산
|
||||
total_api_value = sum(item['total_value'] for item in items)
|
||||
|
||||
print()
|
||||
print(f"전체 약재 수: {len(items)}개")
|
||||
print(f"API 계산 총액: ₩{total_api_value:,.0f}")
|
||||
print()
|
||||
|
||||
# 직접 inventory_lots에서 계산
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
SUM(quantity_onhand * unit_price_per_g) as direct_total
|
||||
FROM inventory_lots
|
||||
WHERE is_depleted = 0 AND quantity_onhand > 0
|
||||
""")
|
||||
direct_total = cursor.fetchone()['direct_total'] or 0
|
||||
|
||||
print(f"직접 계산 총액: ₩{direct_total:,.0f}")
|
||||
print(f"차이: ₩{total_api_value - direct_total:,.0f}")
|
||||
print()
|
||||
|
||||
# 차이 원인 분석
|
||||
if abs(total_api_value - direct_total) > 1:
|
||||
print("차이 원인 분석:")
|
||||
print("-" * 40)
|
||||
|
||||
# 중복 LOT 확인
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
h.herb_name,
|
||||
COUNT(*) as lot_count,
|
||||
SUM(il.quantity_onhand * il.unit_price_per_g) as total_value
|
||||
FROM herb_items h
|
||||
JOIN inventory_lots il ON h.herb_item_id = il.herb_item_id
|
||||
WHERE il.is_depleted = 0 AND il.quantity_onhand > 0
|
||||
GROUP BY h.herb_item_id
|
||||
HAVING lot_count > 1
|
||||
ORDER BY total_value DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
|
||||
multi_lots = cursor.fetchall()
|
||||
if multi_lots:
|
||||
print("\n여러 LOT을 가진 약재:")
|
||||
for herb in multi_lots:
|
||||
print(f" - {herb['herb_name']}: {herb['lot_count']}개 LOT, ₩{herb['total_value']:,.0f}")
|
||||
|
||||
# 특이사항 확인 - LEFT JOIN으로 인한 NULL 처리
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) as herbs_without_lots
|
||||
FROM herb_items h
|
||||
LEFT JOIN inventory_lots il ON h.herb_item_id = il.herb_item_id
|
||||
AND il.is_depleted = 0
|
||||
AND il.quantity_onhand > 0
|
||||
WHERE il.lot_id IS NULL
|
||||
""")
|
||||
no_lots = cursor.fetchone()['herbs_without_lots']
|
||||
|
||||
if no_lots > 0:
|
||||
print(f"\n재고가 없는 약재 수: {no_lots}개")
|
||||
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
debug_api_calculation()
|
||||
193
dev_scripts/final_inventory_analysis.py
Normal file
193
dev_scripts/final_inventory_analysis.py
Normal file
@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
재고 자산 금액 불일치 최종 분석
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
|
||||
def final_analysis():
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
print("=" * 80)
|
||||
print("재고 자산 금액 불일치 최종 분석")
|
||||
print("분석 시간:", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
# 1. 현재 DB의 실제 재고 자산
|
||||
print("📊 현재 데이터베이스 상태")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
SUM(quantity_onhand * unit_price_per_g) as total_value,
|
||||
COUNT(*) as lot_count,
|
||||
SUM(quantity_onhand) as total_quantity
|
||||
FROM inventory_lots
|
||||
WHERE is_depleted = 0 AND quantity_onhand > 0
|
||||
""")
|
||||
|
||||
current = cursor.fetchone()
|
||||
db_total = current['total_value'] or 0
|
||||
|
||||
print(f" DB 재고 자산: ₩{db_total:,.0f}")
|
||||
print(f" 활성 LOT: {current['lot_count']}개")
|
||||
print(f" 총 재고량: {current['total_quantity']:,.1f}g")
|
||||
print()
|
||||
|
||||
# 2. 입고와 출고 분석
|
||||
print("💼 입고/출고 분석")
|
||||
print("-" * 60)
|
||||
|
||||
# 입고 총액
|
||||
cursor.execute("""
|
||||
SELECT SUM(line_total) as total_in
|
||||
FROM purchase_receipt_lines
|
||||
""")
|
||||
total_in = cursor.fetchone()['total_in'] or 0
|
||||
|
||||
# 복합제 소비 금액
|
||||
cursor.execute("""
|
||||
SELECT SUM(cc.quantity_used * il.unit_price_per_g) as total_out
|
||||
FROM compound_consumptions cc
|
||||
JOIN inventory_lots il ON cc.lot_id = il.lot_id
|
||||
""")
|
||||
total_out = cursor.fetchone()['total_out'] or 0
|
||||
|
||||
print(f" 총 입고 금액: ₩{total_in:,.0f}")
|
||||
print(f" 총 소비 금액: ₩{total_out:,.0f}")
|
||||
print(f" 예상 잔액: ₩{total_in - total_out:,.0f}")
|
||||
print()
|
||||
|
||||
# 3. 차이 분석
|
||||
print("🔍 차이 분석 결과")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
ui_value = 5875708 # 화면에 표시되는 금액
|
||||
expected_value = total_in - total_out
|
||||
|
||||
print(f" 화면 표시 금액: ₩{ui_value:,.0f}")
|
||||
print(f" DB 계산 금액: ₩{db_total:,.0f}")
|
||||
print(f" 예상 금액 (입고-소비): ₩{expected_value:,.0f}")
|
||||
print()
|
||||
|
||||
print(" 차이:")
|
||||
print(f" 화면 vs DB: ₩{ui_value - db_total:,.0f}")
|
||||
print(f" 화면 vs 예상: ₩{ui_value - expected_value:,.0f}")
|
||||
print(f" DB vs 예상: ₩{db_total - expected_value:,.0f}")
|
||||
print()
|
||||
|
||||
# 4. 가능한 원인 분석
|
||||
print("❗ 불일치 원인 분석")
|
||||
print("-" * 60)
|
||||
|
||||
# 4-1. 단가 차이 확인
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
prl.line_id,
|
||||
h.herb_name,
|
||||
prl.quantity_g as purchase_qty,
|
||||
prl.unit_price_per_g as purchase_price,
|
||||
prl.line_total as purchase_total,
|
||||
il.quantity_onhand as current_qty,
|
||||
il.unit_price_per_g as lot_price,
|
||||
il.quantity_onhand * il.unit_price_per_g as current_value,
|
||||
ABS(prl.unit_price_per_g - il.unit_price_per_g) as price_diff
|
||||
FROM purchase_receipt_lines prl
|
||||
JOIN inventory_lots il ON prl.line_id = il.receipt_line_id
|
||||
JOIN herb_items h ON prl.herb_item_id = h.herb_item_id
|
||||
WHERE il.is_depleted = 0 AND il.quantity_onhand > 0
|
||||
AND ABS(prl.unit_price_per_g - il.unit_price_per_g) > 0.01
|
||||
ORDER BY price_diff DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
|
||||
price_diffs = cursor.fetchall()
|
||||
if price_diffs:
|
||||
print("\n ⚠️ 입고 단가와 LOT 단가가 다른 항목:")
|
||||
for pd in price_diffs:
|
||||
print(f" {pd['herb_name']}:")
|
||||
print(f" 입고 단가: ₩{pd['purchase_price']:.2f}/g")
|
||||
print(f" LOT 단가: ₩{pd['lot_price']:.2f}/g")
|
||||
print(f" 차이: ₩{pd['price_diff']:.2f}/g")
|
||||
|
||||
# 4-2. 소비 후 남은 재고 확인
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
h.herb_name,
|
||||
il.lot_number,
|
||||
il.quantity_received as original_qty,
|
||||
il.quantity_onhand as current_qty,
|
||||
il.quantity_received - il.quantity_onhand as consumed_qty,
|
||||
il.unit_price_per_g
|
||||
FROM inventory_lots il
|
||||
JOIN herb_items h ON il.herb_item_id = h.herb_item_id
|
||||
WHERE il.is_depleted = 0
|
||||
AND il.quantity_received > il.quantity_onhand
|
||||
ORDER BY (il.quantity_received - il.quantity_onhand) DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
|
||||
consumed_lots = cursor.fetchall()
|
||||
if consumed_lots:
|
||||
print("\n 📉 소비된 재고가 있는 LOT (상위 5개):")
|
||||
for cl in consumed_lots:
|
||||
print(f" {cl['herb_name']} (LOT: {cl['lot_number']})")
|
||||
print(f" 원래: {cl['original_qty']:,.0f}g → 현재: {cl['current_qty']:,.0f}g")
|
||||
print(f" 소비: {cl['consumed_qty']:,.0f}g (₩{cl['consumed_qty'] * cl['unit_price_per_g']:,.0f})")
|
||||
|
||||
# 4-3. JavaScript 계산 로직 확인 필요
|
||||
print("\n 💡 추가 확인 필요사항:")
|
||||
print(" 1) 프론트엔드 JavaScript에서 재고 자산을 계산하는 로직")
|
||||
print(" 2) 캐시 또는 세션 스토리지에 저장된 이전 값")
|
||||
print(" 3) inventory_lots_v2 테이블 사용 여부")
|
||||
|
||||
# inventory_lots_v2 확인
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
SUM(quantity_onhand * unit_price_per_g) as v2_total,
|
||||
COUNT(*) as v2_count
|
||||
FROM inventory_lots_v2
|
||||
WHERE is_depleted = 0 AND quantity_onhand > 0
|
||||
""")
|
||||
|
||||
v2_result = cursor.fetchone()
|
||||
if v2_result and v2_result['v2_count'] > 0:
|
||||
v2_total = v2_result['v2_total'] or 0
|
||||
print(f"\n ⚠️ inventory_lots_v2 테이블 데이터:")
|
||||
print(f" 재고 자산: ₩{v2_total:,.0f}")
|
||||
print(f" LOT 수: {v2_result['v2_count']}개")
|
||||
|
||||
if abs(v2_total - ui_value) < 100:
|
||||
print(f" → 화면 금액과 일치할 가능성 높음!")
|
||||
|
||||
print()
|
||||
|
||||
# 5. 결론
|
||||
print("📝 결론")
|
||||
print("=" * 60)
|
||||
|
||||
diff = ui_value - db_total
|
||||
if diff > 0:
|
||||
print(f" 화면에 표시되는 금액(₩{ui_value:,.0f})이")
|
||||
print(f" 실제 DB 금액(₩{db_total:,.0f})보다")
|
||||
print(f" ₩{diff:,.0f} 더 많습니다.")
|
||||
print()
|
||||
print(" 가능한 원인:")
|
||||
print(" 1) 프론트엔드에서 별도의 계산 로직 사용")
|
||||
print(" 2) 캐시된 이전 데이터 표시")
|
||||
print(" 3) inventory_lots_v2 테이블 참조")
|
||||
print(" 4) 재고 보정 내역이 즉시 반영되지 않음")
|
||||
else:
|
||||
print(f" 실제 DB 금액이 화면 표시 금액보다 적습니다.")
|
||||
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
final_analysis()
|
||||
183
dev_scripts/final_price_analysis.py
Normal file
183
dev_scripts/final_price_analysis.py
Normal file
@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
최종 가격 차이 분석
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
|
||||
def final_price_analysis():
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
print("=" * 80)
|
||||
print("📊 재고 자산 차이 최종 분석")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
# 1. 핵심 차이 확인
|
||||
print("1. 핵심 금액 차이")
|
||||
print("-" * 60)
|
||||
|
||||
# 입고 라인과 LOT 차이
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
h.herb_name,
|
||||
prl.quantity_g as receipt_qty,
|
||||
prl.unit_price_per_g as receipt_price,
|
||||
prl.line_total as receipt_total,
|
||||
il.quantity_received as lot_qty,
|
||||
il.unit_price_per_g as lot_price,
|
||||
il.quantity_received * il.unit_price_per_g as lot_total,
|
||||
prl.line_total - (il.quantity_received * il.unit_price_per_g) as diff
|
||||
FROM purchase_receipt_lines prl
|
||||
JOIN inventory_lots il ON prl.line_id = il.receipt_line_id
|
||||
JOIN herb_items h ON prl.herb_item_id = h.herb_item_id
|
||||
WHERE ABS(prl.line_total - (il.quantity_received * il.unit_price_per_g)) > 1
|
||||
ORDER BY ABS(prl.line_total - (il.quantity_received * il.unit_price_per_g)) DESC
|
||||
""")
|
||||
|
||||
differences = cursor.fetchall()
|
||||
|
||||
if differences:
|
||||
print(" 입고장과 LOT 생성 시 차이가 있는 항목:")
|
||||
print()
|
||||
total_diff = 0
|
||||
for diff in differences:
|
||||
print(f" 📌 {diff['herb_name']}")
|
||||
print(f" 입고장: {diff['receipt_qty']:,.0f}g × ₩{diff['receipt_price']:.2f} = ₩{diff['receipt_total']:,.0f}")
|
||||
print(f" LOT: {diff['lot_qty']:,.0f}g × ₩{diff['lot_price']:.2f} = ₩{diff['lot_total']:,.0f}")
|
||||
print(f" 차이: ₩{diff['diff']:,.0f}")
|
||||
print()
|
||||
total_diff += diff['diff']
|
||||
|
||||
print(f" 총 차이: ₩{total_diff:,.0f}")
|
||||
|
||||
# 2. 재고 자산 흐름
|
||||
print("\n2. 재고 자산 흐름 정리")
|
||||
print("=" * 60)
|
||||
|
||||
# 각 단계별 금액
|
||||
cursor.execute("SELECT SUM(line_total) as total FROM purchase_receipt_lines")
|
||||
receipt_total = cursor.fetchone()['total'] or 0
|
||||
|
||||
cursor.execute("""
|
||||
SELECT SUM(quantity_received * unit_price_per_g) as total
|
||||
FROM inventory_lots
|
||||
""")
|
||||
lot_creation_total = cursor.fetchone()['total'] or 0
|
||||
|
||||
cursor.execute("""
|
||||
SELECT SUM(cc.quantity_used * il.unit_price_per_g) as total
|
||||
FROM compound_consumptions cc
|
||||
JOIN inventory_lots il ON cc.lot_id = il.lot_id
|
||||
""")
|
||||
consumed_total = cursor.fetchone()['total'] or 0
|
||||
|
||||
cursor.execute("""
|
||||
SELECT SUM(quantity_onhand * unit_price_per_g) as total
|
||||
FROM inventory_lots
|
||||
WHERE is_depleted = 0 AND quantity_onhand > 0
|
||||
""")
|
||||
current_inventory = cursor.fetchone()['total'] or 0
|
||||
|
||||
print(f" 1️⃣ 입고장 총액: ₩{receipt_total:,.0f}")
|
||||
print(f" 2️⃣ LOT 생성 총액: ₩{lot_creation_total:,.0f}")
|
||||
print(f" 차이 (1-2): ₩{receipt_total - lot_creation_total:,.0f}")
|
||||
print()
|
||||
print(f" 3️⃣ 소비 총액: ₩{consumed_total:,.0f}")
|
||||
print(f" 4️⃣ 현재 재고 자산: ₩{current_inventory:,.0f}")
|
||||
print()
|
||||
print(f" 📊 계산식:")
|
||||
print(f" LOT 생성 - 소비 = ₩{lot_creation_total:,.0f} - ₩{consumed_total:,.0f}")
|
||||
print(f" = ₩{lot_creation_total - consumed_total:,.0f} (예상)")
|
||||
print(f" 실제 재고 = ₩{current_inventory:,.0f}")
|
||||
print(f" 차이 = ₩{current_inventory - (lot_creation_total - consumed_total):,.0f}")
|
||||
|
||||
# 3. 차이 원인 분석
|
||||
print("\n3. 차이 원인 설명")
|
||||
print("-" * 60)
|
||||
|
||||
# 휴먼일당귀 특별 케이스 확인
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
prl.quantity_g as receipt_qty,
|
||||
il.quantity_received as lot_received,
|
||||
il.quantity_onhand as lot_current
|
||||
FROM purchase_receipt_lines prl
|
||||
JOIN inventory_lots il ON prl.line_id = il.receipt_line_id
|
||||
JOIN herb_items h ON prl.herb_item_id = h.herb_item_id
|
||||
WHERE h.herb_name = '휴먼일당귀'
|
||||
""")
|
||||
|
||||
ildan = cursor.fetchone()
|
||||
if ildan:
|
||||
print("\n 💡 휴먼일당귀 케이스:")
|
||||
print(f" 입고장 수량: {ildan['receipt_qty']:,.0f}g")
|
||||
print(f" LOT 생성 수량: {ildan['lot_received']:,.0f}g")
|
||||
print(f" 현재 재고: {ildan['lot_current']:,.0f}g")
|
||||
print(f" → 입고 시 5,000g 중 3,000g만 LOT 생성됨")
|
||||
print(f" → 나머지 2,000g는 별도 처리되었을 가능성")
|
||||
|
||||
print("\n 📝 결론:")
|
||||
print(" 1. 입고장 총액 (₩1,616,400) vs LOT 생성 총액 (₩1,607,400)")
|
||||
print(" → ₩9,000 차이 (휴먼일당귀 수량 차이로 인함)")
|
||||
print()
|
||||
print(" 2. 예상 재고 (₩1,529,434) vs 실제 재고 (₩1,529,434)")
|
||||
print(" → 정확히 일치")
|
||||
print()
|
||||
print(" 3. 입고 기준 예상 (₩1,538,434) vs 실제 재고 (₩1,529,434)")
|
||||
print(" → ₩9,000 차이 (입고와 LOT 생성 차이와 동일)")
|
||||
|
||||
# 4. 추가 LOT 확인
|
||||
print("\n4. 추가 LOT 존재 여부")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
h.herb_name,
|
||||
COUNT(*) as lot_count,
|
||||
SUM(il.quantity_received) as total_received,
|
||||
SUM(il.quantity_onhand) as total_onhand
|
||||
FROM inventory_lots il
|
||||
JOIN herb_items h ON il.herb_item_id = h.herb_item_id
|
||||
WHERE h.herb_name = '휴먼일당귀'
|
||||
GROUP BY h.herb_item_id
|
||||
""")
|
||||
|
||||
ildan_lots = cursor.fetchone()
|
||||
if ildan_lots:
|
||||
print(f" 휴먼일당귀 LOT 현황:")
|
||||
print(f" LOT 개수: {ildan_lots['lot_count']}개")
|
||||
print(f" 총 입고량: {ildan_lots['total_received']:,.0f}g")
|
||||
print(f" 현재 재고: {ildan_lots['total_onhand']:,.0f}g")
|
||||
|
||||
# 상세 LOT 정보
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
lot_id,
|
||||
lot_number,
|
||||
quantity_received,
|
||||
quantity_onhand,
|
||||
unit_price_per_g,
|
||||
receipt_line_id
|
||||
FROM inventory_lots il
|
||||
JOIN herb_items h ON il.herb_item_id = h.herb_item_id
|
||||
WHERE h.herb_name = '휴먼일당귀'
|
||||
""")
|
||||
|
||||
lots = cursor.fetchall()
|
||||
for lot in lots:
|
||||
print(f"\n LOT {lot['lot_id']}:")
|
||||
print(f" LOT 번호: {lot['lot_number']}")
|
||||
print(f" 입고량: {lot['quantity_received']:,.0f}g")
|
||||
print(f" 현재: {lot['quantity_onhand']:,.0f}g")
|
||||
print(f" 단가: ₩{lot['unit_price_per_g']:.2f}")
|
||||
print(f" 입고라인: {lot['receipt_line_id']}")
|
||||
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
final_price_analysis()
|
||||
145
dev_scripts/final_verification.py
Normal file
145
dev_scripts/final_verification.py
Normal file
@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
최종 검증 - 문제 해결 확인
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import json
|
||||
import urllib.request
|
||||
|
||||
def final_verification():
|
||||
print("=" * 80)
|
||||
print("📊 재고 자산 문제 해결 최종 검증")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
# 1. API 호출 결과
|
||||
print("1. API 응답 확인")
|
||||
print("-" * 60)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen('http://localhost:5001/api/inventory/summary') as response:
|
||||
data = json.loads(response.read())
|
||||
api_value = data['summary']['total_value']
|
||||
total_items = data['summary']['total_items']
|
||||
|
||||
print(f" API 재고 자산: ₩{api_value:,.0f}")
|
||||
print(f" 총 약재 수: {total_items}개")
|
||||
except Exception as e:
|
||||
print(f" API 호출 실패: {e}")
|
||||
api_value = 0
|
||||
|
||||
# 2. 데이터베이스 직접 계산
|
||||
print("\n2. 데이터베이스 직접 계산")
|
||||
print("-" * 60)
|
||||
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
SUM(quantity_onhand * unit_price_per_g) as total_value,
|
||||
COUNT(*) as lot_count,
|
||||
SUM(quantity_onhand) as total_quantity
|
||||
FROM inventory_lots
|
||||
WHERE is_depleted = 0 AND quantity_onhand > 0
|
||||
""")
|
||||
|
||||
db_result = cursor.fetchone()
|
||||
db_value = db_result['total_value'] or 0
|
||||
|
||||
print(f" DB 재고 자산: ₩{db_value:,.0f}")
|
||||
print(f" 활성 LOT: {db_result['lot_count']}개")
|
||||
print(f" 총 재고량: {db_result['total_quantity']:,.1f}g")
|
||||
|
||||
# 3. 입고와 출고 기반 계산
|
||||
print("\n3. 입고/출고 기반 계산")
|
||||
print("-" * 60)
|
||||
|
||||
# 총 입고액
|
||||
cursor.execute("SELECT SUM(line_total) as total FROM purchase_receipt_lines")
|
||||
total_in = cursor.fetchone()['total'] or 0
|
||||
|
||||
# 총 소비액
|
||||
cursor.execute("""
|
||||
SELECT SUM(cc.quantity_used * il.unit_price_per_g) as total
|
||||
FROM compound_consumptions cc
|
||||
JOIN inventory_lots il ON cc.lot_id = il.lot_id
|
||||
""")
|
||||
total_out = cursor.fetchone()['total'] or 0
|
||||
|
||||
expected = total_in - total_out
|
||||
|
||||
print(f" 입고 총액: ₩{total_in:,.0f}")
|
||||
print(f" 소비 총액: ₩{total_out:,.0f}")
|
||||
print(f" 예상 재고: ₩{expected:,.0f}")
|
||||
|
||||
# 4. 결과 비교
|
||||
print("\n4. 결과 비교")
|
||||
print("=" * 60)
|
||||
|
||||
print(f"\n 🎯 API 재고 자산: ₩{api_value:,.0f}")
|
||||
print(f" 🎯 DB 직접 계산: ₩{db_value:,.0f}")
|
||||
print(f" 🎯 예상 재고액: ₩{expected:,.0f}")
|
||||
|
||||
# 차이 계산
|
||||
api_db_diff = abs(api_value - db_value)
|
||||
db_expected_diff = abs(db_value - expected)
|
||||
|
||||
print(f"\n API vs DB 차이: ₩{api_db_diff:,.0f}")
|
||||
print(f" DB vs 예상 차이: ₩{db_expected_diff:,.0f}")
|
||||
|
||||
# 5. 결론
|
||||
print("\n5. 결론")
|
||||
print("=" * 60)
|
||||
|
||||
if api_db_diff < 100:
|
||||
print("\n ✅ 문제 해결 완료!")
|
||||
print(" API와 DB 계산이 일치합니다.")
|
||||
print(f" 재고 자산: ₩{api_value:,.0f}")
|
||||
else:
|
||||
print("\n ⚠️ 아직 차이가 있습니다.")
|
||||
print(f" 차이: ₩{api_db_diff:,.0f}")
|
||||
|
||||
if db_expected_diff > 100000:
|
||||
print("\n 📌 참고: DB 재고와 예상 재고 간 차이는")
|
||||
print(" 다음 요인들로 인해 발생할 수 있습니다:")
|
||||
print(" - 입고 시점과 LOT 생성 시점의 단가 차이")
|
||||
print(" - 재고 보정 내역")
|
||||
print(" - 반올림 오차 누적")
|
||||
|
||||
# 6. 효능 태그 확인 (중복 문제가 해결되었는지)
|
||||
print("\n6. 효능 태그 표시 확인")
|
||||
print("-" * 60)
|
||||
|
||||
# API에서 효능 태그가 있는 약재 확인
|
||||
try:
|
||||
with urllib.request.urlopen('http://localhost:5001/api/inventory/summary') as response:
|
||||
data = json.loads(response.read())
|
||||
|
||||
herbs_with_tags = [
|
||||
item for item in data['data']
|
||||
if item.get('efficacy_tags') and len(item['efficacy_tags']) > 0
|
||||
]
|
||||
|
||||
print(f" 효능 태그가 있는 약재: {len(herbs_with_tags)}개")
|
||||
|
||||
if herbs_with_tags:
|
||||
sample = herbs_with_tags[0]
|
||||
print(f"\n 예시: {sample['herb_name']}")
|
||||
print(f" 태그: {', '.join(sample['efficacy_tags'])}")
|
||||
print(f" 재고 가치: ₩{sample['total_value']:,.0f}")
|
||||
except Exception as e:
|
||||
print(f" 효능 태그 확인 실패: {e}")
|
||||
|
||||
conn.close()
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("검증 완료")
|
||||
print("=" * 80)
|
||||
|
||||
if __name__ == "__main__":
|
||||
final_verification()
|
||||
84
dev_scripts/test_direct_api.py
Normal file
84
dev_scripts/test_direct_api.py
Normal file
@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
API 함수 직접 테스트
|
||||
"""
|
||||
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
# Flask 앱과 동일한 설정
|
||||
DATABASE = 'database/kdrug.db'
|
||||
|
||||
def get_inventory_summary():
|
||||
"""app.py의 get_inventory_summary 함수와 동일"""
|
||||
conn = sqlite3.connect(DATABASE)
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
h.herb_item_id,
|
||||
h.insurance_code,
|
||||
h.herb_name,
|
||||
COALESCE(SUM(il.quantity_onhand), 0) as total_quantity,
|
||||
COUNT(DISTINCT il.lot_id) as lot_count,
|
||||
COUNT(DISTINCT il.origin_country) as origin_count,
|
||||
AVG(il.unit_price_per_g) as avg_price,
|
||||
MIN(il.unit_price_per_g) as min_price,
|
||||
MAX(il.unit_price_per_g) as max_price,
|
||||
COALESCE(SUM(il.quantity_onhand * il.unit_price_per_g), 0) as total_value,
|
||||
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
|
||||
LEFT JOIN herb_products hp ON h.insurance_code = hp.product_code
|
||||
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
|
||||
ORDER BY h.herb_name
|
||||
""")
|
||||
|
||||
inventory = []
|
||||
for row in cursor.fetchall():
|
||||
item = dict(row)
|
||||
if item['efficacy_tags']:
|
||||
item['efficacy_tags'] = item['efficacy_tags'].split(',')
|
||||
else:
|
||||
item['efficacy_tags'] = []
|
||||
inventory.append(item)
|
||||
|
||||
# 전체 요약
|
||||
total_value = sum(item['total_value'] for item in inventory)
|
||||
total_items = len(inventory)
|
||||
|
||||
print("=" * 60)
|
||||
print("API 함수 직접 실행 결과")
|
||||
print("=" * 60)
|
||||
print()
|
||||
print(f"총 약재 수: {total_items}개")
|
||||
print(f"총 재고 자산: ₩{total_value:,.0f}")
|
||||
print()
|
||||
|
||||
# 상세 내역
|
||||
print("약재별 재고 가치 (상위 10개):")
|
||||
print("-" * 40)
|
||||
sorted_items = sorted(inventory, key=lambda x: x['total_value'], reverse=True)
|
||||
for i, item in enumerate(sorted_items[:10], 1):
|
||||
print(f"{i:2}. {item['herb_name']:15} ₩{item['total_value']:10,.0f}")
|
||||
|
||||
conn.close()
|
||||
return total_value
|
||||
|
||||
if __name__ == "__main__":
|
||||
total = get_inventory_summary()
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(f"최종 결과: ₩{total:,.0f}")
|
||||
|
||||
if total == 5875708:
|
||||
print("⚠️ API와 동일한 값이 나옴!")
|
||||
else:
|
||||
print(f"✅ 예상값: ₩1,529,434")
|
||||
print(f" 차이: ₩{total - 1529434:,.0f}")
|
||||
192
dev_scripts/test_herb_info_page.py
Normal file
192
dev_scripts/test_herb_info_page.py
Normal file
@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env python3
|
||||
"""약재 정보 페이지 테스트 - 렌더링 문제 수정 후 검증"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
BASE_URL = "http://localhost:5001"
|
||||
|
||||
|
||||
def test_html_structure():
|
||||
"""HTML 구조 검증 - herb-info가 content-area 안에 있는지 확인"""
|
||||
print("1. HTML 구조 검증...")
|
||||
response = requests.get(f"{BASE_URL}/")
|
||||
if response.status_code != 200:
|
||||
print(f" FAIL: 페이지 로드 실패 {response.status_code}")
|
||||
return False
|
||||
|
||||
content = response.text
|
||||
|
||||
# herb-info가 col-md-10 content-area 안에 있는지 확인
|
||||
idx_content = content.find('col-md-10 content-area')
|
||||
idx_herb_info = content.find('id="herb-info"')
|
||||
|
||||
if idx_content < 0:
|
||||
print(" FAIL: col-md-10 content-area 찾을 수 없음")
|
||||
return False
|
||||
if idx_herb_info < 0:
|
||||
print(" FAIL: herb-info div 찾을 수 없음")
|
||||
return False
|
||||
|
||||
if idx_herb_info > idx_content:
|
||||
print(" PASS: herb-info가 content-area 안에 올바르게 위치함")
|
||||
else:
|
||||
print(" FAIL: herb-info가 content-area 밖에 있음!")
|
||||
return False
|
||||
|
||||
# efficacyFilter ID 중복 검사
|
||||
count_efficacy = content.count('id="efficacyFilter"')
|
||||
if count_efficacy > 1:
|
||||
print(f" FAIL: id=\"efficacyFilter\" 중복 {count_efficacy}개 발견!")
|
||||
return False
|
||||
else:
|
||||
print(f" PASS: id=\"efficacyFilter\" 중복 없음 (개수: {count_efficacy})")
|
||||
|
||||
# herbInfoEfficacyFilter 존재 확인
|
||||
if 'id="herbInfoEfficacyFilter"' in content:
|
||||
print(" PASS: herbInfoEfficacyFilter ID 정상 존재")
|
||||
else:
|
||||
print(" FAIL: herbInfoEfficacyFilter ID 없음!")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def test_efficacy_tags():
|
||||
"""효능 태그 조회 API 검증"""
|
||||
print("\n2. 효능 태그 목록 조회...")
|
||||
response = requests.get(f"{BASE_URL}/api/efficacy-tags")
|
||||
if response.status_code != 200:
|
||||
print(f" FAIL: {response.status_code}")
|
||||
return False
|
||||
|
||||
tags = response.json()
|
||||
if not isinstance(tags, list):
|
||||
print(f" FAIL: 응답이 리스트가 아님 - {type(tags)}")
|
||||
return False
|
||||
|
||||
print(f" PASS: {len(tags)}개의 효능 태그 조회 성공")
|
||||
for tag in tags[:3]:
|
||||
print(f" - {tag.get('name', '')}: {tag.get('description', '')}")
|
||||
return True
|
||||
|
||||
|
||||
def test_herb_masters_api():
|
||||
"""약재 마스터 목록 + herb_id 포함 여부 검증"""
|
||||
print("\n3. 약재 마스터 목록 조회 (herb_id 포함 여부 확인)...")
|
||||
response = requests.get(f"{BASE_URL}/api/herbs/masters")
|
||||
if response.status_code != 200:
|
||||
print(f" FAIL: {response.status_code}")
|
||||
return False
|
||||
|
||||
result = response.json()
|
||||
if not result.get('success'):
|
||||
print(f" FAIL: success=False")
|
||||
return False
|
||||
|
||||
herbs = result.get('data', [])
|
||||
print(f" PASS: {len(herbs)}개의 약재 조회 성공")
|
||||
|
||||
if not herbs:
|
||||
print(" FAIL: 약재 데이터 없음")
|
||||
return False
|
||||
|
||||
first = herbs[0]
|
||||
|
||||
# herb_id 확인
|
||||
if 'herb_id' in first:
|
||||
print(f" PASS: herb_id 필드 존재 (값: {first['herb_id']})")
|
||||
else:
|
||||
print(f" FAIL: herb_id 필드 누락! 키 목록: {list(first.keys())}")
|
||||
return False
|
||||
|
||||
# ingredient_code 확인
|
||||
if 'ingredient_code' in first:
|
||||
print(f" PASS: ingredient_code 필드 존재")
|
||||
else:
|
||||
print(" FAIL: ingredient_code 필드 누락!")
|
||||
return False
|
||||
|
||||
# efficacy_tags가 리스트인지 확인
|
||||
if isinstance(first.get('efficacy_tags'), list):
|
||||
print(f" PASS: efficacy_tags가 리스트 형식")
|
||||
else:
|
||||
print(f" FAIL: efficacy_tags 형식 오류: {first.get('efficacy_tags')}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def test_herb_extended_info():
|
||||
"""약재 확장 정보 조회 API 검증"""
|
||||
print("\n4. 약재 확장 정보 조회 (herb_id=1 기준)...")
|
||||
response = requests.get(f"{BASE_URL}/api/herbs/1/extended")
|
||||
if response.status_code != 200:
|
||||
print(f" FAIL: {response.status_code}")
|
||||
return False
|
||||
|
||||
info = response.json()
|
||||
if not isinstance(info, dict):
|
||||
print(f" FAIL: 응답이 dict가 아님")
|
||||
return False
|
||||
|
||||
print(f" PASS: herb_id=1 확장 정보 조회 성공")
|
||||
print(f" - herb_name: {info.get('herb_name', '-')}")
|
||||
print(f" - name_korean: {info.get('name_korean', '-')}")
|
||||
print(f" - property: {info.get('property', '-')}")
|
||||
return True
|
||||
|
||||
|
||||
def test_herb_masters_has_extended_fields():
|
||||
"""약재 마스터 목록에 확장 정보(property, main_effects)가 포함되는지 검증"""
|
||||
print("\n5. 약재 마스터에 확장 정보 필드 포함 여부...")
|
||||
response = requests.get(f"{BASE_URL}/api/herbs/masters")
|
||||
result = response.json()
|
||||
herbs = result.get('data', [])
|
||||
|
||||
required_fields = ['ingredient_code', 'herb_name', 'herb_id', 'has_stock',
|
||||
'efficacy_tags', 'property', 'main_effects']
|
||||
first = herbs[0] if herbs else {}
|
||||
missing = [f for f in required_fields if f not in first]
|
||||
|
||||
if missing:
|
||||
print(f" FAIL: 누락된 필드: {missing}")
|
||||
return False
|
||||
|
||||
print(f" PASS: 필수 필드 모두 존재: {required_fields}")
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
print("=== 약재 정보 페이지 렌더링 수정 검증 테스트 ===")
|
||||
print(f"시간: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print(f"서버: {BASE_URL}")
|
||||
print("-" * 50)
|
||||
|
||||
results = []
|
||||
results.append(("HTML 구조 검증", test_html_structure()))
|
||||
results.append(("효능 태그 API", test_efficacy_tags()))
|
||||
results.append(("약재 마스터 API (herb_id)", test_herb_masters_api()))
|
||||
results.append(("약재 확장 정보 API", test_herb_extended_info()))
|
||||
results.append(("약재 마스터 필드 완전성", test_herb_masters_has_extended_fields()))
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
success = sum(1 for _, r in results if r)
|
||||
total = len(results)
|
||||
print(f"테스트 결과: {success}/{total} 성공")
|
||||
for name, result in results:
|
||||
status = "PASS" if result else "FAIL"
|
||||
print(f" [{status}] {name}")
|
||||
|
||||
if success == total:
|
||||
print("\n모든 테스트 통과. 약재 정보 페이지가 정상적으로 동작해야 합니다.")
|
||||
else:
|
||||
print(f"\n{total - success}개 테스트 실패. 추가 수정이 필요합니다.")
|
||||
|
||||
return success == total
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
162
dev_scripts/test_herb_info_ui.py
Normal file
162
dev_scripts/test_herb_info_ui.py
Normal file
@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Playwright를 사용한 약재 정보 페이지 UI 테스트"""
|
||||
|
||||
import asyncio
|
||||
from playwright.async_api import async_playwright
|
||||
import time
|
||||
|
||||
async def test_herb_info_page():
|
||||
async with async_playwright() as p:
|
||||
# 브라우저 시작
|
||||
browser = await p.chromium.launch(headless=True)
|
||||
page = await browser.new_page()
|
||||
|
||||
# 콘솔 메시지 캡처
|
||||
console_messages = []
|
||||
page.on("console", lambda msg: console_messages.append(f"{msg.type}: {msg.text}"))
|
||||
|
||||
# 페이지 에러 캡처
|
||||
page_errors = []
|
||||
page.on("pageerror", lambda err: page_errors.append(str(err)))
|
||||
|
||||
try:
|
||||
print("=== Playwright 약재 정보 페이지 테스트 ===\n")
|
||||
|
||||
# 1. 메인 페이지 접속
|
||||
print("1. 메인 페이지 접속...")
|
||||
await page.goto("http://localhost:5001")
|
||||
await page.wait_for_load_state("networkidle")
|
||||
|
||||
# 2. 약재 정보 메뉴 클릭
|
||||
print("2. 약재 정보 메뉴 클릭...")
|
||||
herb_info_link = page.locator('a[data-page="herb-info"]')
|
||||
is_visible = await herb_info_link.is_visible()
|
||||
print(f" - 약재 정보 메뉴 표시 여부: {is_visible}")
|
||||
|
||||
if is_visible:
|
||||
await herb_info_link.click()
|
||||
await page.wait_for_timeout(2000) # 2초 대기
|
||||
|
||||
# 3. herb-info 페이지 표시 확인
|
||||
print("\n3. 약재 정보 페이지 요소 확인...")
|
||||
herb_info_div = page.locator('#herb-info')
|
||||
is_herb_info_visible = await herb_info_div.is_visible()
|
||||
print(f" - herb-info div 표시: {is_herb_info_visible}")
|
||||
|
||||
if is_herb_info_visible:
|
||||
# 검색 섹션 확인
|
||||
search_section = page.locator('#herb-search-section')
|
||||
is_search_visible = await search_section.is_visible()
|
||||
print(f" - 검색 섹션 표시: {is_search_visible}")
|
||||
|
||||
# 약재 카드 그리드 확인
|
||||
herb_grid = page.locator('#herbInfoGrid')
|
||||
is_grid_visible = await herb_grid.is_visible()
|
||||
print(f" - 약재 그리드 표시: {is_grid_visible}")
|
||||
|
||||
# 약재 카드 개수 확인
|
||||
await page.wait_for_selector('.herb-info-card', timeout=5000)
|
||||
herb_cards = await page.locator('.herb-info-card').count()
|
||||
print(f" - 표시된 약재 카드 수: {herb_cards}개")
|
||||
|
||||
if herb_cards > 0:
|
||||
# 첫 번째 약재 카드 정보 확인
|
||||
first_card = page.locator('.herb-info-card').first
|
||||
card_title = await first_card.locator('.card-title').text_content()
|
||||
print(f" - 첫 번째 약재: {card_title}")
|
||||
|
||||
# 카드 클릭으로 상세 보기 (카드 전체가 클릭 가능)
|
||||
print("\n4. 약재 상세 정보 확인...")
|
||||
# herb-info-card는 클릭 가능한 카드이므로 직접 클릭
|
||||
if True:
|
||||
await first_card.click()
|
||||
await page.wait_for_timeout(1000)
|
||||
|
||||
# 상세 모달 확인
|
||||
modal = page.locator('#herbDetailModal')
|
||||
is_modal_visible = await modal.is_visible()
|
||||
print(f" - 상세 모달 표시: {is_modal_visible}")
|
||||
|
||||
if is_modal_visible:
|
||||
modal_title = await modal.locator('.modal-title').text_content()
|
||||
print(f" - 모달 제목: {modal_title}")
|
||||
|
||||
# 모달 닫기
|
||||
close_btn = modal.locator('button.btn-close')
|
||||
if await close_btn.is_visible():
|
||||
await close_btn.click()
|
||||
await page.wait_for_timeout(500)
|
||||
|
||||
# 5. 검색 기능 테스트
|
||||
print("\n5. 검색 기능 테스트...")
|
||||
search_input = page.locator('#herbSearchInput')
|
||||
if await search_input.is_visible():
|
||||
await search_input.fill("감초")
|
||||
await page.locator('#herbSearchBtn').click()
|
||||
await page.wait_for_timeout(1000)
|
||||
|
||||
search_result_count = await page.locator('.herb-info-card').count()
|
||||
print(f" - '감초' 검색 결과: {search_result_count}개")
|
||||
|
||||
# 6. 효능별 보기 테스트
|
||||
print("\n6. 효능별 보기 전환...")
|
||||
efficacy_btn = page.locator('button[data-view="efficacy"]')
|
||||
if await efficacy_btn.is_visible():
|
||||
await efficacy_btn.click()
|
||||
await page.wait_for_timeout(1000)
|
||||
|
||||
efficacy_section = page.locator('#herb-efficacy-section')
|
||||
is_efficacy_visible = await efficacy_section.is_visible()
|
||||
print(f" - 효능별 섹션 표시: {is_efficacy_visible}")
|
||||
|
||||
if is_efficacy_visible:
|
||||
tag_buttons = await page.locator('.efficacy-tag-btn').count()
|
||||
print(f" - 효능 태그 버튼 수: {tag_buttons}개")
|
||||
else:
|
||||
print(" ⚠️ herb-info div가 표시되지 않음!")
|
||||
|
||||
# 디버깅: 현재 활성 페이지 확인
|
||||
active_pages = await page.locator('.main-content.active').count()
|
||||
print(f" - 활성 페이지 수: {active_pages}")
|
||||
|
||||
# 디버깅: herb-info의 display 스타일 확인
|
||||
herb_info_style = await herb_info_div.get_attribute('style')
|
||||
print(f" - herb-info style: {herb_info_style}")
|
||||
|
||||
# 디버깅: herb-info의 클래스 확인
|
||||
herb_info_classes = await herb_info_div.get_attribute('class')
|
||||
print(f" - herb-info classes: {herb_info_classes}")
|
||||
|
||||
# 7. 콘솔 에러 확인
|
||||
print("\n7. 콘솔 메시지 확인...")
|
||||
if console_messages:
|
||||
print(" 콘솔 메시지:")
|
||||
for msg in console_messages[:10]: # 처음 10개만 출력
|
||||
print(f" - {msg}")
|
||||
else:
|
||||
print(" ✓ 콘솔 메시지 없음")
|
||||
|
||||
if page_errors:
|
||||
print(" ⚠️ 페이지 에러:")
|
||||
for err in page_errors:
|
||||
print(f" - {err}")
|
||||
else:
|
||||
print(" ✓ 페이지 에러 없음")
|
||||
|
||||
# 스크린샷 저장
|
||||
await page.screenshot(path="/root/kdrug/herb_info_page.png")
|
||||
print("\n스크린샷 저장: /root/kdrug/herb_info_page.png")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 테스트 실패: {e}")
|
||||
# 에러 시 스크린샷
|
||||
await page.screenshot(path="/root/kdrug/herb_info_error.png")
|
||||
print("에러 스크린샷 저장: /root/kdrug/herb_info_error.png")
|
||||
|
||||
finally:
|
||||
await browser.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Playwright 테스트 시작...\n")
|
||||
asyncio.run(test_herb_info_page())
|
||||
print("\n테스트 완료!")
|
||||
134
dev_scripts/test_js_debug.py
Normal file
134
dev_scripts/test_js_debug.py
Normal file
@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
"""JavaScript 디버깅을 위한 Playwright 테스트"""
|
||||
|
||||
import asyncio
|
||||
from playwright.async_api import async_playwright
|
||||
|
||||
async def debug_herb_info():
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.launch(headless=True)
|
||||
page = await browser.new_page()
|
||||
|
||||
# 콘솔 메시지 캡처
|
||||
console_messages = []
|
||||
page.on("console", lambda msg: console_messages.append({
|
||||
"type": msg.type,
|
||||
"text": msg.text,
|
||||
"args": msg.args
|
||||
}))
|
||||
|
||||
# 네트워크 요청 캡처
|
||||
network_requests = []
|
||||
page.on("request", lambda req: network_requests.append({
|
||||
"url": req.url,
|
||||
"method": req.method
|
||||
}))
|
||||
|
||||
# 네트워크 응답 캡처
|
||||
network_responses = []
|
||||
async def log_response(response):
|
||||
if "/api/" in response.url:
|
||||
try:
|
||||
body = await response.text()
|
||||
network_responses.append({
|
||||
"url": response.url,
|
||||
"status": response.status,
|
||||
"body": body[:200] if body else None
|
||||
})
|
||||
except:
|
||||
pass
|
||||
page.on("response", log_response)
|
||||
|
||||
try:
|
||||
# 페이지 접속
|
||||
print("페이지 접속 중...")
|
||||
await page.goto("http://localhost:5001")
|
||||
await page.wait_for_load_state("networkidle")
|
||||
|
||||
# JavaScript 실행하여 직접 함수 호출
|
||||
print("\n직접 JavaScript 함수 테스트...")
|
||||
|
||||
# loadHerbInfo 함수 존재 확인
|
||||
has_function = await page.evaluate("typeof loadHerbInfo === 'function'")
|
||||
print(f"1. loadHerbInfo 함수 존재: {has_function}")
|
||||
|
||||
# loadAllHerbsInfo 함수 존재 확인
|
||||
has_all_herbs = await page.evaluate("typeof loadAllHerbsInfo === 'function'")
|
||||
print(f"2. loadAllHerbsInfo 함수 존재: {has_all_herbs}")
|
||||
|
||||
# displayHerbCards 함수 존재 확인
|
||||
has_display = await page.evaluate("typeof displayHerbCards === 'function'")
|
||||
print(f"3. displayHerbCards 함수 존재: {has_display}")
|
||||
|
||||
# 약재 정보 페이지로 이동
|
||||
await page.click('a[data-page="herb-info"]')
|
||||
await page.wait_for_timeout(2000)
|
||||
|
||||
# herbInfoGrid 요소 확인
|
||||
grid_exists = await page.evaluate("document.getElementById('herbInfoGrid') !== null")
|
||||
print(f"4. herbInfoGrid 요소 존재: {grid_exists}")
|
||||
|
||||
# herbInfoGrid 내용 확인
|
||||
grid_html = await page.evaluate("document.getElementById('herbInfoGrid')?.innerHTML || 'EMPTY'")
|
||||
print(f"5. herbInfoGrid 내용 길이: {len(grid_html)} 문자")
|
||||
if grid_html and grid_html != 'EMPTY':
|
||||
print(f" 처음 100자: {grid_html[:100]}...")
|
||||
|
||||
# API 호출 직접 테스트
|
||||
print("\n\nAPI 응답 직접 테스트...")
|
||||
api_response = await page.evaluate("""
|
||||
fetch('/api/herbs/masters')
|
||||
.then(res => res.json())
|
||||
.then(data => ({
|
||||
success: data.success,
|
||||
dataLength: data.data ? data.data.length : 0,
|
||||
firstItem: data.data ? data.data[0] : null
|
||||
}))
|
||||
.catch(err => ({ error: err.toString() }))
|
||||
""")
|
||||
print(f"API 응답: {api_response}")
|
||||
|
||||
# displayHerbCards 직접 호출 테스트
|
||||
if api_response.get('dataLength', 0) > 0:
|
||||
print("\n\ndisplayHerbCards 직접 호출...")
|
||||
await page.evaluate("""
|
||||
fetch('/api/herbs/masters')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (typeof displayHerbCards === 'function') {
|
||||
displayHerbCards(data.data);
|
||||
} else {
|
||||
console.error('displayHerbCards 함수가 없습니다');
|
||||
}
|
||||
})
|
||||
""")
|
||||
await page.wait_for_timeout(1000)
|
||||
|
||||
# 다시 확인
|
||||
grid_html_after = await page.evaluate("document.getElementById('herbInfoGrid')?.innerHTML || 'EMPTY'")
|
||||
print(f"displayHerbCards 호출 후 내용 길이: {len(grid_html_after)} 문자")
|
||||
|
||||
card_count = await page.evaluate("document.querySelectorAll('.herb-card').length")
|
||||
print(f"herb-card 요소 개수: {card_count}")
|
||||
|
||||
# 콘솔 메시지 출력
|
||||
print("\n\n=== 콘솔 메시지 ===")
|
||||
for msg in console_messages:
|
||||
if 'error' in msg['type'].lower():
|
||||
print(f"❌ {msg['type']}: {msg['text']}")
|
||||
else:
|
||||
print(f"📝 {msg['type']}: {msg['text']}")
|
||||
|
||||
# API 응답 상태 확인
|
||||
print("\n\n=== API 응답 ===")
|
||||
for resp in network_responses:
|
||||
if '/api/herbs/masters' in resp['url']:
|
||||
print(f"URL: {resp['url']}")
|
||||
print(f"상태: {resp['status']}")
|
||||
print(f"응답: {resp['body'][:100] if resp['body'] else 'No body'}")
|
||||
|
||||
finally:
|
||||
await browser.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(debug_herb_info())
|
||||
192
find_duplicate_issue.py
Normal file
192
find_duplicate_issue.py
Normal file
@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
JOIN으로 인한 중복 문제 찾기
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
|
||||
def find_duplicate_issue():
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
print("=" * 80)
|
||||
print("JOIN으로 인한 중복 문제 분석")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
# 1. 효능 태그 JOIN 없이 계산
|
||||
print("1. 효능 태그 JOIN 없이 계산")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
h.herb_item_id,
|
||||
h.herb_name,
|
||||
COALESCE(SUM(il.quantity_onhand * il.unit_price_per_g), 0) as total_value
|
||||
FROM herb_items h
|
||||
LEFT JOIN inventory_lots il ON h.herb_item_id = il.herb_item_id AND il.is_depleted = 0
|
||||
GROUP BY h.herb_item_id, h.herb_name
|
||||
HAVING total_value > 0
|
||||
ORDER BY total_value DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
|
||||
simple_results = cursor.fetchall()
|
||||
simple_total = 0
|
||||
|
||||
for item in simple_results:
|
||||
simple_total += item['total_value']
|
||||
print(f" {item['herb_name']:15} ₩{item['total_value']:10,.0f}")
|
||||
|
||||
# 전체 합계
|
||||
cursor.execute("""
|
||||
SELECT SUM(total_value) as grand_total
|
||||
FROM (
|
||||
SELECT
|
||||
h.herb_item_id,
|
||||
COALESCE(SUM(il.quantity_onhand * il.unit_price_per_g), 0) as total_value
|
||||
FROM herb_items h
|
||||
LEFT JOIN inventory_lots il ON h.herb_item_id = il.herb_item_id AND il.is_depleted = 0
|
||||
GROUP BY h.herb_item_id
|
||||
HAVING total_value > 0
|
||||
)
|
||||
""")
|
||||
|
||||
simple_grand_total = cursor.fetchone()['grand_total'] or 0
|
||||
print(f"\n 총합: ₩{simple_grand_total:,.0f}")
|
||||
|
||||
# 2. 효능 태그 JOIN 포함 계산 (API와 동일)
|
||||
print("\n2. 효능 태그 JOIN 포함 계산 (API 쿼리)")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
h.herb_item_id,
|
||||
h.herb_name,
|
||||
COALESCE(SUM(il.quantity_onhand * il.unit_price_per_g), 0) as total_value,
|
||||
COUNT(*) as row_count
|
||||
FROM herb_items h
|
||||
LEFT JOIN inventory_lots il ON h.herb_item_id = il.herb_item_id AND il.is_depleted = 0
|
||||
LEFT JOIN herb_products hp ON h.insurance_code = hp.product_code
|
||||
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.herb_name
|
||||
HAVING total_value > 0
|
||||
ORDER BY total_value DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
|
||||
api_results = cursor.fetchall()
|
||||
|
||||
for item in api_results:
|
||||
print(f" {item['herb_name']:15} ₩{item['total_value']:10,.0f} (행수: {item['row_count']})")
|
||||
|
||||
# 전체 합계 (API 방식)
|
||||
cursor.execute("""
|
||||
SELECT SUM(total_value) as grand_total
|
||||
FROM (
|
||||
SELECT
|
||||
h.herb_item_id,
|
||||
COALESCE(SUM(il.quantity_onhand * il.unit_price_per_g), 0) as total_value
|
||||
FROM herb_items h
|
||||
LEFT JOIN inventory_lots il ON h.herb_item_id = il.herb_item_id AND il.is_depleted = 0
|
||||
LEFT JOIN herb_products hp ON h.insurance_code = hp.product_code
|
||||
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
|
||||
HAVING total_value > 0
|
||||
)
|
||||
""")
|
||||
|
||||
api_grand_total = cursor.fetchone()['grand_total'] or 0
|
||||
print(f"\n 총합: ₩{api_grand_total:,.0f}")
|
||||
|
||||
# 3. 중복 원인 분석
|
||||
print("\n3. 중복 원인 분석")
|
||||
print("-" * 60)
|
||||
|
||||
print(f" ✅ 정상 계산: ₩{simple_grand_total:,.0f}")
|
||||
print(f" ❌ API 계산: ₩{api_grand_total:,.0f}")
|
||||
print(f" 차이: ₩{api_grand_total - simple_grand_total:,.0f}")
|
||||
|
||||
if api_grand_total > simple_grand_total:
|
||||
ratio = api_grand_total / simple_grand_total if simple_grand_total > 0 else 0
|
||||
print(f" 배율: {ratio:.2f}배")
|
||||
|
||||
# 4. 효능 태그 중복 확인
|
||||
print("\n4. 효능 태그로 인한 중복 확인")
|
||||
print("-" * 60)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
h.herb_name,
|
||||
h.ingredient_code,
|
||||
COUNT(DISTINCT hit.tag_id) as tag_count
|
||||
FROM herb_items h
|
||||
LEFT JOIN herb_products hp ON h.insurance_code = hp.product_code
|
||||
LEFT JOIN herb_item_tags hit ON COALESCE(h.ingredient_code, hp.ingredient_code) = hit.ingredient_code
|
||||
WHERE h.herb_item_id IN (
|
||||
SELECT herb_item_id FROM inventory_lots
|
||||
WHERE is_depleted = 0 AND quantity_onhand > 0
|
||||
)
|
||||
GROUP BY h.herb_item_id
|
||||
HAVING tag_count > 1
|
||||
ORDER BY tag_count DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
|
||||
multi_tags = cursor.fetchall()
|
||||
|
||||
if multi_tags:
|
||||
print(" 여러 효능 태그를 가진 약재:")
|
||||
for herb in multi_tags:
|
||||
print(f" - {herb['herb_name']}: {herb['tag_count']}개 태그")
|
||||
|
||||
# 5. 특정 약재 상세 분석 (휴먼감초)
|
||||
print("\n5. 휴먼감초 상세 분석")
|
||||
print("-" * 60)
|
||||
|
||||
# 정상 계산
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
il.lot_id,
|
||||
il.quantity_onhand,
|
||||
il.unit_price_per_g,
|
||||
il.quantity_onhand * il.unit_price_per_g as value
|
||||
FROM inventory_lots il
|
||||
JOIN herb_items h ON il.herb_item_id = h.herb_item_id
|
||||
WHERE h.herb_name = '휴먼감초' AND il.is_depleted = 0
|
||||
""")
|
||||
|
||||
gamcho_lots = cursor.fetchall()
|
||||
actual_total = sum(lot['value'] for lot in gamcho_lots)
|
||||
|
||||
print(f" 실제 LOT 수: {len(gamcho_lots)}개")
|
||||
for lot in gamcho_lots:
|
||||
print(f" LOT {lot['lot_id']}: {lot['quantity_onhand']}g × ₩{lot['unit_price_per_g']} = ₩{lot['value']:,.0f}")
|
||||
print(f" 실제 합계: ₩{actual_total:,.0f}")
|
||||
|
||||
# JOIN 포함 계산
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) as join_rows
|
||||
FROM herb_items h
|
||||
LEFT JOIN inventory_lots il ON h.herb_item_id = il.herb_item_id AND il.is_depleted = 0
|
||||
LEFT JOIN herb_products hp ON h.insurance_code = hp.product_code
|
||||
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
|
||||
WHERE h.herb_name = '휴먼감초' AND il.lot_id IS NOT NULL
|
||||
""")
|
||||
|
||||
join_rows = cursor.fetchone()['join_rows']
|
||||
print(f"\n JOIN 후 행 수: {join_rows}행")
|
||||
|
||||
if join_rows > len(gamcho_lots):
|
||||
print(f" ⚠️ 중복 발생! {join_rows / len(gamcho_lots):.1f}배로 뻥튀기됨")
|
||||
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
find_duplicate_issue()
|
||||
43
get_ingredient_codes.py
Normal file
43
get_ingredient_codes.py
Normal file
@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
"""데이터베이스에서 실제 ingredient_code 확인"""
|
||||
|
||||
import sqlite3
|
||||
|
||||
conn = sqlite3.connect('database/kdrug.db')
|
||||
cur = conn.cursor()
|
||||
|
||||
# herb_items와 herb_products를 조인하여 ingredient_code 확인
|
||||
cur.execute("""
|
||||
SELECT DISTINCT
|
||||
hi.herb_name,
|
||||
COALESCE(hi.ingredient_code, hp.ingredient_code) as ingredient_code,
|
||||
hi.insurance_code
|
||||
FROM herb_items hi
|
||||
LEFT JOIN herb_products hp ON hi.insurance_code = hp.product_code
|
||||
WHERE COALESCE(hi.ingredient_code, hp.ingredient_code) IS NOT NULL
|
||||
ORDER BY hi.herb_name
|
||||
""")
|
||||
|
||||
print("=== 실제 약재 ingredient_code 목록 ===")
|
||||
herbs = cur.fetchall()
|
||||
for herb in herbs:
|
||||
print(f"{herb[0]:10s} -> {herb[1]} (보험코드: {herb[2]})")
|
||||
|
||||
# 십전대보탕 구성 약재들 확인
|
||||
target_herbs = ['인삼', '백출', '복령', '감초', '숙지황', '작약', '천궁', '당귀', '황기', '육계']
|
||||
print(f"\n=== 십전대보탕 구성 약재 ({len(target_herbs)}개) ===")
|
||||
for target in target_herbs:
|
||||
cur.execute("""
|
||||
SELECT hi.herb_name,
|
||||
COALESCE(hi.ingredient_code, hp.ingredient_code) as code
|
||||
FROM herb_items hi
|
||||
LEFT JOIN herb_products hp ON hi.insurance_code = hp.product_code
|
||||
WHERE hi.herb_name = ?
|
||||
""", (target,))
|
||||
result = cur.fetchone()
|
||||
if result and result[1]:
|
||||
print(f"✓ {result[0]:6s} -> {result[1]}")
|
||||
else:
|
||||
print(f"✗ {target:6s} -> ingredient_code 없음")
|
||||
|
||||
conn.close()
|
||||
@ -245,7 +245,7 @@ def create_disease_herb_mapping():
|
||||
recommendation_grade VARCHAR(10), -- 권고등급
|
||||
|
||||
clinical_notes TEXT,
|
||||
references TEXT,
|
||||
reference_sources TEXT,
|
||||
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
|
||||
55
sample/자산관련.md
Normal file
55
sample/자산관련.md
Normal file
@ -0,0 +1,55 @@
|
||||
Traceback (most recent call last):
|
||||
File "/root/kdrug/analyze_inventory_full.py", line 315, in <module>
|
||||
analyze_inventory_discrepancy()
|
||||
File "/root/kdrug/analyze_inventory_full.py", line 261, in analyze_inventory_discrepancy
|
||||
cursor.execute("""
|
||||
sqlite3.OperationalError: no such column: quantity
|
||||
|
||||
================================================================================
|
||||
재고 자산 금액 불일치 상세 분석
|
||||
분석 시간: 2026-02-18 01:23:14
|
||||
================================================================================
|
||||
|
||||
1. 현재 시스템 재고 자산 (inventory_lots 테이블)
|
||||
------------------------------------------------------------
|
||||
💰 총 재고 자산: ₩1,529,434
|
||||
📦 활성 LOT 수: 30개
|
||||
⚖️ 총 재고량: 86,420.0g
|
||||
🌿 약재 종류: 28종
|
||||
|
||||
2. 입고장 데이터 분석 (purchase_receipts + purchase_receipt_lines)
|
||||
------------------------------------------------------------
|
||||
📋 총 입고 금액: ₩1,551,900
|
||||
📑 입고장 수: 1건
|
||||
📝 입고 라인 수: 29개
|
||||
⚖️ 총 입고량: 88,000.0g
|
||||
|
||||
최근 입고장 5건:
|
||||
- PR-20260211-0001 (20260211): ₩1,551,900
|
||||
|
||||
3. LOT-입고장 매칭 분석
|
||||
------------------------------------------------------------
|
||||
✅ 입고장과 연결된 LOT: 30개 (₩1,529,434)
|
||||
❌ 입고장 없는 LOT: 0개 (₩0)
|
||||
|
||||
4. 입고장 라인별 LOT 생성 확인
|
||||
------------------------------------------------------------
|
||||
📝 전체 입고 라인: 30개
|
||||
✅ LOT 생성된 라인: 30개
|
||||
❌ LOT 없는 라인: 0개
|
||||
|
||||
5. 재고 자산 차이 분석
|
||||
------------------------------------------------------------
|
||||
💰 현재 LOT 재고 가치: ₩1,529,434
|
||||
📋 원본 입고 금액: ₩1,616,400
|
||||
📊 차이: ₩-86,966
|
||||
|
||||
6. 출고 및 소비 내역
|
||||
------------------------------------------------------------
|
||||
처방전 테이블이 없습니다.
|
||||
🏭 복합제 소비 금액: ₩77,966
|
||||
⚖️ 복합제 소비량: 4,580.0g
|
||||
📦 복합제 수: 8개
|
||||
|
||||
7. 재고 보정 내역
|
||||
------------------------------------------------------------
|
||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
BIN
screenshots/herb_info_error.png
Normal file
BIN
screenshots/herb_info_error.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
BIN
screenshots/herb_info_page.png
Normal file
BIN
screenshots/herb_info_page.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
3172
static/app.js.backup
Normal file
3172
static/app.js.backup
Normal file
File diff suppressed because it is too large
Load Diff
1578
templates/index.html.backup
Normal file
1578
templates/index.html.backup
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user