feat: 실시간 커스텀 처방(가감방) 감지 시스템 구현
- 프론트엔드: 조제 시 실시간 커스텀 처방 감지 - 처방 선택 시 원래 구성 약재 저장 - 약재 추가/삭제/변경 시 즉시 감지 - 가감방 뱃지 및 변경 내용 표시 - 백엔드: 커스텀 처방 자동 감지 및 저장 - compounds 테이블에 커스텀 관련 필드 추가 - 조제 시 원 처방과 비교하여 변경사항 자동 감지 - 커스텀 처방 정보 저장 (추가/제거/변경된 약재) - 환자 조제 내역에 커스텀 처방 표시 - 가감방 뱃지 표시 - 변경 내용 상세 표시 - DB 마이그레이션 스크립트 추가 - is_custom, custom_summary, custom_type 필드 추가 - compound_ingredients에 modification_type, original_grams 필드 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d6410fa273
commit
1441c01fb4
127
app.py
127
app.py
@ -1117,7 +1117,10 @@ def get_patient_compounds(patient_id):
|
|||||||
c.status,
|
c.status,
|
||||||
c.notes,
|
c.notes,
|
||||||
c.created_at,
|
c.created_at,
|
||||||
c.created_by
|
c.created_by,
|
||||||
|
c.is_custom,
|
||||||
|
c.custom_summary,
|
||||||
|
c.custom_type
|
||||||
FROM compounds c
|
FROM compounds c
|
||||||
LEFT JOIN formulas f ON c.formula_id = f.formula_id
|
LEFT JOIN formulas f ON c.formula_id = f.formula_id
|
||||||
WHERE c.patient_id = ?
|
WHERE c.patient_id = ?
|
||||||
@ -1144,29 +1147,117 @@ def get_patient_compounds(patient_id):
|
|||||||
|
|
||||||
@app.route('/api/compounds', methods=['POST'])
|
@app.route('/api/compounds', methods=['POST'])
|
||||||
def create_compound():
|
def create_compound():
|
||||||
"""조제 실행"""
|
"""조제 실행 - 커스텀 처방 감지 포함"""
|
||||||
try:
|
try:
|
||||||
data = request.json
|
data = request.json
|
||||||
|
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
# 조제 마스터 생성
|
formula_id = data.get('formula_id')
|
||||||
|
|
||||||
|
# 커스텀 처방 감지를 위한 준비
|
||||||
|
is_custom = False
|
||||||
|
custom_summary = ""
|
||||||
|
custom_details = {
|
||||||
|
'added': [],
|
||||||
|
'removed': [],
|
||||||
|
'modified': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# formula_id가 있는 경우 원 처방과 비교
|
||||||
|
if formula_id:
|
||||||
|
# 원 처방 구성 조회 (ingredient_code 기반)
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT fi.ingredient_code, hm.herb_name, fi.grams_per_cheop
|
||||||
|
FROM formula_ingredients fi
|
||||||
|
JOIN herb_masters hm ON fi.ingredient_code = hm.ingredient_code
|
||||||
|
WHERE fi.formula_id = ?
|
||||||
|
""", (formula_id,))
|
||||||
|
|
||||||
|
# ingredient_code -> herb_item_id 매핑
|
||||||
|
original_by_code = {}
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
ingredient_code = row[0]
|
||||||
|
herb_name = row[1]
|
||||||
|
grams = row[2]
|
||||||
|
|
||||||
|
# 해당 ingredient_code를 가진 herb_item_id들 조회
|
||||||
|
cursor.execute("SELECT herb_item_id FROM herb_items WHERE ingredient_code = ?",
|
||||||
|
(ingredient_code,))
|
||||||
|
herb_ids = [r[0] for r in cursor.fetchall()]
|
||||||
|
|
||||||
|
for herb_id in herb_ids:
|
||||||
|
original_by_code[herb_id] = {
|
||||||
|
'herb_name': herb_name,
|
||||||
|
'grams': grams,
|
||||||
|
'ingredient_code': ingredient_code
|
||||||
|
}
|
||||||
|
|
||||||
|
original_ingredients = original_by_code
|
||||||
|
|
||||||
|
# 실제 조제 구성과 비교
|
||||||
|
actual_ingredients = {ing['herb_item_id']: ing['grams_per_cheop']
|
||||||
|
for ing in data['ingredients']}
|
||||||
|
|
||||||
|
# 추가된 약재 확인
|
||||||
|
for ing in data['ingredients']:
|
||||||
|
herb_id = ing['herb_item_id']
|
||||||
|
if herb_id not in original_ingredients:
|
||||||
|
# 약재명 조회
|
||||||
|
cursor.execute("SELECT herb_name FROM herb_items WHERE herb_item_id = ?", (herb_id,))
|
||||||
|
herb_name = cursor.fetchone()[0]
|
||||||
|
custom_details['added'].append(f"{herb_name} {ing['grams_per_cheop']}g")
|
||||||
|
is_custom = True
|
||||||
|
|
||||||
|
# 제거된 약재 확인
|
||||||
|
for herb_id, info in original_ingredients.items():
|
||||||
|
if herb_id not in actual_ingredients:
|
||||||
|
custom_details['removed'].append(info['herb_name'])
|
||||||
|
is_custom = True
|
||||||
|
|
||||||
|
# 용량 변경된 약재 확인
|
||||||
|
for herb_id, original_info in original_ingredients.items():
|
||||||
|
if herb_id in actual_ingredients:
|
||||||
|
original_grams = original_info['grams']
|
||||||
|
actual_grams = actual_ingredients[herb_id]
|
||||||
|
if abs(original_grams - actual_grams) > 0.01:
|
||||||
|
custom_details['modified'].append(
|
||||||
|
f"{original_info['herb_name']} {original_grams}g→{actual_grams}g"
|
||||||
|
)
|
||||||
|
is_custom = True
|
||||||
|
|
||||||
|
# 커스텀 요약 생성
|
||||||
|
summary_parts = []
|
||||||
|
if custom_details['added']:
|
||||||
|
summary_parts.append(f"추가: {', '.join(custom_details['added'])}")
|
||||||
|
if custom_details['removed']:
|
||||||
|
summary_parts.append(f"제거: {', '.join(custom_details['removed'])}")
|
||||||
|
if custom_details['modified']:
|
||||||
|
summary_parts.append(f"변경: {', '.join(custom_details['modified'])}")
|
||||||
|
|
||||||
|
custom_summary = " | ".join(summary_parts) if summary_parts else ""
|
||||||
|
|
||||||
|
# 조제 마스터 생성 (커스텀 정보 포함)
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
INSERT INTO compounds (patient_id, formula_id, compound_date,
|
INSERT INTO compounds (patient_id, formula_id, compound_date,
|
||||||
je_count, cheop_total, pouch_total,
|
je_count, cheop_total, pouch_total,
|
||||||
prescription_no, notes, created_by)
|
prescription_no, notes, created_by,
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
is_custom, custom_summary, custom_type)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""", (
|
""", (
|
||||||
data.get('patient_id'),
|
data.get('patient_id'),
|
||||||
data.get('formula_id'),
|
formula_id,
|
||||||
data.get('compound_date', datetime.now().strftime('%Y-%m-%d')),
|
data.get('compound_date', datetime.now().strftime('%Y-%m-%d')),
|
||||||
data['je_count'],
|
data['je_count'],
|
||||||
data['cheop_total'],
|
data['cheop_total'],
|
||||||
data['pouch_total'],
|
data['pouch_total'],
|
||||||
data.get('prescription_no'),
|
data.get('prescription_no'),
|
||||||
data.get('notes'),
|
data.get('notes'),
|
||||||
data.get('created_by', 'system')
|
data.get('created_by', 'system'),
|
||||||
|
1 if is_custom else 0,
|
||||||
|
custom_summary if is_custom else None,
|
||||||
|
'custom' if is_custom else 'standard'
|
||||||
))
|
))
|
||||||
compound_id = cursor.lastrowid
|
compound_id = cursor.lastrowid
|
||||||
|
|
||||||
@ -1178,13 +1269,27 @@ def create_compound():
|
|||||||
total_grams = ingredient['total_grams']
|
total_grams = ingredient['total_grams']
|
||||||
origin_country = ingredient.get('origin_country') # 원산지 선택 정보
|
origin_country = ingredient.get('origin_country') # 원산지 선택 정보
|
||||||
|
|
||||||
# 조제 약재 구성 기록
|
# modification_type 결정
|
||||||
|
modification_type = 'original'
|
||||||
|
original_grams = None
|
||||||
|
|
||||||
|
if formula_id and herb_item_id in original_ingredients:
|
||||||
|
orig_g = original_ingredients[herb_item_id]['grams']
|
||||||
|
if abs(orig_g - ingredient['grams_per_cheop']) > 0.01:
|
||||||
|
modification_type = 'modified'
|
||||||
|
original_grams = orig_g
|
||||||
|
elif formula_id and herb_item_id not in original_ingredients:
|
||||||
|
modification_type = 'added'
|
||||||
|
|
||||||
|
# 조제 약재 구성 기록 (커스텀 정보 포함)
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
INSERT INTO compound_ingredients (compound_id, herb_item_id,
|
INSERT INTO compound_ingredients (compound_id, herb_item_id,
|
||||||
grams_per_cheop, total_grams)
|
grams_per_cheop, total_grams,
|
||||||
VALUES (?, ?, ?, ?)
|
modification_type, original_grams)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
""", (compound_id, herb_item_id,
|
""", (compound_id, herb_item_id,
|
||||||
ingredient['grams_per_cheop'], total_grams))
|
ingredient['grams_per_cheop'], total_grams,
|
||||||
|
modification_type, original_grams))
|
||||||
|
|
||||||
# 재고 차감 (FIFO 방식 - 원산지 지정 시 해당 원산지만)
|
# 재고 차감 (FIFO 방식 - 원산지 지정 시 해당 원산지만)
|
||||||
remaining_qty = total_grams
|
remaining_qty = total_grams
|
||||||
|
|||||||
224
check_custom_prescription.py
Normal file
224
check_custom_prescription.py
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
커스텀 처방 감지 유틸리티
|
||||||
|
조제 시 원 처방과 다른 구성인지 확인
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
|
def get_connection():
|
||||||
|
"""데이터베이스 연결"""
|
||||||
|
return sqlite3.connect('database/kdrug.db')
|
||||||
|
|
||||||
|
def check_custom_prescription(compound_id: int) -> Tuple[bool, Dict]:
|
||||||
|
"""
|
||||||
|
조제가 원 처방과 다른지 확인
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(is_custom, differences_dict)
|
||||||
|
"""
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# 1. compound의 formula_id 가져오기
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT c.formula_id, f.formula_name
|
||||||
|
FROM compounds c
|
||||||
|
JOIN formulas f ON c.formula_id = f.formula_id
|
||||||
|
WHERE c.compound_id = ?
|
||||||
|
""", (compound_id,))
|
||||||
|
|
||||||
|
result = cursor.fetchone()
|
||||||
|
if not result:
|
||||||
|
conn.close()
|
||||||
|
return False, {"error": "Compound not found"}
|
||||||
|
|
||||||
|
formula_id, formula_name = result
|
||||||
|
|
||||||
|
# 2. 원 처방의 구성 약재
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT
|
||||||
|
fi.herb_item_id,
|
||||||
|
h.herb_name,
|
||||||
|
fi.grams_per_cheop
|
||||||
|
FROM formula_ingredients fi
|
||||||
|
JOIN herb_items h ON fi.herb_item_id = h.herb_item_id
|
||||||
|
WHERE fi.formula_id = ?
|
||||||
|
ORDER BY fi.herb_item_id
|
||||||
|
""", (formula_id,))
|
||||||
|
|
||||||
|
original_ingredients = {row[0]: {
|
||||||
|
'herb_name': row[1],
|
||||||
|
'grams_per_cheop': row[2]
|
||||||
|
} for row in cursor.fetchall()}
|
||||||
|
|
||||||
|
# 3. 실제 조제된 구성 약재
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT
|
||||||
|
ci.herb_item_id,
|
||||||
|
h.herb_name,
|
||||||
|
ci.grams_per_cheop
|
||||||
|
FROM compound_ingredients ci
|
||||||
|
JOIN herb_items h ON ci.herb_item_id = h.herb_item_id
|
||||||
|
WHERE ci.compound_id = ?
|
||||||
|
ORDER BY ci.herb_item_id
|
||||||
|
""", (compound_id,))
|
||||||
|
|
||||||
|
actual_ingredients = {row[0]: {
|
||||||
|
'herb_name': row[1],
|
||||||
|
'grams_per_cheop': row[2]
|
||||||
|
} for row in cursor.fetchall()}
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# 4. 비교 분석
|
||||||
|
differences = {
|
||||||
|
'formula_name': formula_name,
|
||||||
|
'added': [],
|
||||||
|
'removed': [],
|
||||||
|
'modified': [],
|
||||||
|
'is_custom': False
|
||||||
|
}
|
||||||
|
|
||||||
|
# 추가된 약재
|
||||||
|
for herb_id, info in actual_ingredients.items():
|
||||||
|
if herb_id not in original_ingredients:
|
||||||
|
differences['added'].append({
|
||||||
|
'herb_id': herb_id,
|
||||||
|
'herb_name': info['herb_name'],
|
||||||
|
'grams_per_cheop': info['grams_per_cheop']
|
||||||
|
})
|
||||||
|
differences['is_custom'] = True
|
||||||
|
|
||||||
|
# 제거된 약재
|
||||||
|
for herb_id, info in original_ingredients.items():
|
||||||
|
if herb_id not in actual_ingredients:
|
||||||
|
differences['removed'].append({
|
||||||
|
'herb_id': herb_id,
|
||||||
|
'herb_name': info['herb_name'],
|
||||||
|
'grams_per_cheop': info['grams_per_cheop']
|
||||||
|
})
|
||||||
|
differences['is_custom'] = True
|
||||||
|
|
||||||
|
# 용량 변경된 약재
|
||||||
|
for herb_id in set(original_ingredients.keys()) & set(actual_ingredients.keys()):
|
||||||
|
orig_grams = original_ingredients[herb_id]['grams_per_cheop']
|
||||||
|
actual_grams = actual_ingredients[herb_id]['grams_per_cheop']
|
||||||
|
|
||||||
|
if abs(orig_grams - actual_grams) > 0.01: # 부동소수점 오차 고려
|
||||||
|
differences['modified'].append({
|
||||||
|
'herb_id': herb_id,
|
||||||
|
'herb_name': original_ingredients[herb_id]['herb_name'],
|
||||||
|
'original_grams': orig_grams,
|
||||||
|
'actual_grams': actual_grams,
|
||||||
|
'difference': actual_grams - orig_grams
|
||||||
|
})
|
||||||
|
differences['is_custom'] = True
|
||||||
|
|
||||||
|
return differences['is_custom'], differences
|
||||||
|
|
||||||
|
def generate_custom_summary(differences: Dict) -> str:
|
||||||
|
"""커스텀 내역을 요약 문자열로 생성"""
|
||||||
|
summary_parts = []
|
||||||
|
|
||||||
|
# 추가
|
||||||
|
if differences['added']:
|
||||||
|
added_herbs = [f"{item['herb_name']} {item['grams_per_cheop']}g"
|
||||||
|
for item in differences['added']]
|
||||||
|
summary_parts.append(f"추가: {', '.join(added_herbs)}")
|
||||||
|
|
||||||
|
# 제거
|
||||||
|
if differences['removed']:
|
||||||
|
removed_herbs = [item['herb_name'] for item in differences['removed']]
|
||||||
|
summary_parts.append(f"제거: {', '.join(removed_herbs)}")
|
||||||
|
|
||||||
|
# 수정
|
||||||
|
if differences['modified']:
|
||||||
|
modified_herbs = [f"{item['herb_name']} {item['original_grams']}g→{item['actual_grams']}g"
|
||||||
|
for item in differences['modified']]
|
||||||
|
summary_parts.append(f"변경: {', '.join(modified_herbs)}")
|
||||||
|
|
||||||
|
return " | ".join(summary_parts) if summary_parts else "표준 처방"
|
||||||
|
|
||||||
|
def list_all_custom_prescriptions():
|
||||||
|
"""모든 커스텀 처방 찾기"""
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# 모든 조제 목록
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT
|
||||||
|
c.compound_id,
|
||||||
|
c.compound_date,
|
||||||
|
p.name as patient_name,
|
||||||
|
f.formula_name
|
||||||
|
FROM compounds c
|
||||||
|
LEFT JOIN patients p ON c.patient_id = p.patient_id
|
||||||
|
JOIN formulas f ON c.formula_id = f.formula_id
|
||||||
|
ORDER BY c.compound_date DESC
|
||||||
|
""")
|
||||||
|
|
||||||
|
compounds = cursor.fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
custom_compounds = []
|
||||||
|
|
||||||
|
for compound in compounds:
|
||||||
|
compound_id = compound[0]
|
||||||
|
is_custom, differences = check_custom_prescription(compound_id)
|
||||||
|
|
||||||
|
if is_custom:
|
||||||
|
custom_compounds.append({
|
||||||
|
'compound_id': compound_id,
|
||||||
|
'compound_date': compound[1],
|
||||||
|
'patient_name': compound[2],
|
||||||
|
'formula_name': compound[3],
|
||||||
|
'summary': generate_custom_summary(differences),
|
||||||
|
'differences': differences
|
||||||
|
})
|
||||||
|
|
||||||
|
return custom_compounds
|
||||||
|
|
||||||
|
def demo():
|
||||||
|
"""데모 실행"""
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("커스텀 처방 감지 시스템")
|
||||||
|
print("="*80)
|
||||||
|
|
||||||
|
# 전체 커스텀 처방 검색
|
||||||
|
custom_prescriptions = list_all_custom_prescriptions()
|
||||||
|
|
||||||
|
if not custom_prescriptions:
|
||||||
|
print("\n조제 내역이 없거나 모든 조제가 표준 처방입니다.")
|
||||||
|
|
||||||
|
# 테스트용 샘플 데이터 표시
|
||||||
|
print("\n[시뮬레이션] 만약 십전대보탕에 구기자를 추가했다면:")
|
||||||
|
print("-" * 60)
|
||||||
|
|
||||||
|
sample_diff = {
|
||||||
|
'formula_name': '십전대보탕',
|
||||||
|
'added': [{'herb_name': '구기자', 'grams_per_cheop': 3}],
|
||||||
|
'removed': [],
|
||||||
|
'modified': [{'herb_name': '인삼', 'original_grams': 5, 'actual_grams': 7}],
|
||||||
|
'is_custom': True
|
||||||
|
}
|
||||||
|
|
||||||
|
summary = generate_custom_summary(sample_diff)
|
||||||
|
print(f"처방: 십전대보탕 (가감방)")
|
||||||
|
print(f"변경 내역: {summary}")
|
||||||
|
print("\n환자 기록 표시:")
|
||||||
|
print(" 2024-02-17 십전대보탕 가감방 20첩")
|
||||||
|
print(f" └─ {summary}")
|
||||||
|
else:
|
||||||
|
print(f"\n총 {len(custom_prescriptions)}개의 커스텀 처방이 발견되었습니다.\n")
|
||||||
|
|
||||||
|
for cp in custom_prescriptions:
|
||||||
|
print(f"조제 #{cp['compound_id']} | {cp['compound_date']} | {cp['patient_name']}")
|
||||||
|
print(f" 처방: {cp['formula_name']} (가감방)")
|
||||||
|
print(f" 변경: {cp['summary']}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
demo()
|
||||||
310
docs/조제_프로세스_및_커스텀_처방.md
Normal file
310
docs/조제_프로세스_및_커스텀_처방.md
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
# 조제 프로세스 및 커스텀 처방 관리
|
||||||
|
|
||||||
|
## 목차
|
||||||
|
1. [조제 프로세스 흐름](#1-조제-프로세스-흐름)
|
||||||
|
2. [데이터베이스 구조](#2-데이터베이스-구조)
|
||||||
|
3. [커스텀 처방 처리](#3-커스텀-처방-처리)
|
||||||
|
4. [구현 제안사항](#4-구현-제안사항)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 조제 프로세스 흐름
|
||||||
|
|
||||||
|
### 1.1 전체 흐름도
|
||||||
|
```
|
||||||
|
[처방 선택] → [구성 약재 자동 로드] → [약재 커스터마이징] → [재고 확인] → [조제 실행] → [기록 저장]
|
||||||
|
↓ ↓ ↓ ↓ ↓ ↓
|
||||||
|
십전대보탕 formula_ingredients 약재 추가/삭제/수정 inventory_lots 재고 차감 compounds
|
||||||
|
확인 stock_ledger compound_ingredients
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 단계별 상세 프로세스
|
||||||
|
|
||||||
|
#### Step 1: 처방 선택
|
||||||
|
- **테이블**: `formulas`
|
||||||
|
- **주요 필드**:
|
||||||
|
- `formula_id`: 처방 ID
|
||||||
|
- `formula_name`: 처방명 (예: "십전대보탕")
|
||||||
|
- `base_cheop_per_je`: 1제당 기본 첩수 (보통 20첩)
|
||||||
|
|
||||||
|
#### Step 2: 구성 약재 자동 로드
|
||||||
|
- **테이블**: `formula_ingredients`
|
||||||
|
- **동작**: 선택한 처방의 기본 구성 약재를 자동으로 불러옴
|
||||||
|
- **예시**: 십전대보탕 선택 시 인삼, 백출, 복령, 감초 등 10가지 약재 자동 로드
|
||||||
|
|
||||||
|
#### Step 3: 약재 커스터마이징
|
||||||
|
- **가능한 작업**:
|
||||||
|
- ✅ 약재 추가 (예: 구기자 3g 추가)
|
||||||
|
- ✅ 약재 삭제 (특정 약재 제외)
|
||||||
|
- ✅ 용량 수정 (기본 5g → 7g으로 변경)
|
||||||
|
|
||||||
|
#### Step 4: 재고 확인 및 선택
|
||||||
|
- **테이블**: `inventory_lots`
|
||||||
|
- **display_name 표시**: 각 약재의 정확한 variant 확인
|
||||||
|
- 예: "건강" → "건강.土[한국산]" vs "건강[페루산]"
|
||||||
|
- **원산지 선택**: 자동(FIFO) 또는 수동 선택
|
||||||
|
|
||||||
|
#### Step 5: 조제 실행 및 재고 차감
|
||||||
|
- **FIFO 방식**: 오래된 로트부터 우선 소비
|
||||||
|
- **재고 부족 체크**: 부족 시 경고 표시
|
||||||
|
- **로트별 차감**: `compound_consumptions`에 상세 기록
|
||||||
|
|
||||||
|
#### Step 6: 조제 기록 저장
|
||||||
|
- **compounds 테이블**: 조제 마스터 정보
|
||||||
|
- **compound_ingredients 테이블**: 실제 사용된 약재 구성
|
||||||
|
- **compound_consumptions 테이블**: 로트별 차감 내역
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 데이터베이스 구조
|
||||||
|
|
||||||
|
### 2.1 처방 관련 테이블
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 처방 마스터 (기본 처방)
|
||||||
|
formulas
|
||||||
|
├── formula_id (PK)
|
||||||
|
├── formula_name -- "십전대보탕"
|
||||||
|
└── base_cheop_per_je -- 20첩
|
||||||
|
|
||||||
|
-- 처방 기본 구성
|
||||||
|
formula_ingredients
|
||||||
|
├── formula_id (FK)
|
||||||
|
├── herb_item_id (FK)
|
||||||
|
└── grams_per_cheop -- 1첩당 용량
|
||||||
|
|
||||||
|
-- 실제 조제 기록
|
||||||
|
compounds
|
||||||
|
├── compound_id (PK)
|
||||||
|
├── patient_id (FK) -- 환자
|
||||||
|
├── formula_id (FK) -- 원 처방 참조
|
||||||
|
├── compound_date -- 조제일
|
||||||
|
├── cheop_total -- 총 첩수
|
||||||
|
└── notes -- "구기자 3g 추가" 등 커스텀 내역
|
||||||
|
|
||||||
|
-- 실제 사용 약재 (커스텀 포함)
|
||||||
|
compound_ingredients
|
||||||
|
├── compound_id (FK)
|
||||||
|
├── herb_item_id (FK)
|
||||||
|
└── grams_per_cheop -- 실제 사용 용량
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 재고 관련 테이블
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 재고 로트
|
||||||
|
inventory_lots
|
||||||
|
├── lot_id (PK)
|
||||||
|
├── herb_item_id (FK)
|
||||||
|
├── display_name -- "갈근.각", "건강.土" 등
|
||||||
|
├── quantity_onhand -- 현재 재고량
|
||||||
|
└── unit_price_per_g -- g당 단가
|
||||||
|
|
||||||
|
-- 로트 변형 정보
|
||||||
|
lot_variants
|
||||||
|
├── lot_id (FK)
|
||||||
|
├── raw_name -- 상세 제품명
|
||||||
|
├── form -- 형태 (각, 片, 土)
|
||||||
|
├── processing -- 가공법 (9증, 酒炙)
|
||||||
|
└── grade -- 등급 (特, 中, 小)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 커스텀 처방 처리
|
||||||
|
|
||||||
|
### 3.1 현재 시스템의 처리 방식
|
||||||
|
|
||||||
|
현재 시스템은 이미 커스텀 처방을 처리할 수 있는 구조를 가지고 있습니다:
|
||||||
|
|
||||||
|
1. **formula_ingredients**: 처방의 기본 구성 (변경되지 않음)
|
||||||
|
2. **compound_ingredients**: 실제 조제 시 사용된 구성 (커스텀 반영)
|
||||||
|
|
||||||
|
### 3.2 커스텀 처방 식별 방법
|
||||||
|
|
||||||
|
#### 방법 1: 비교를 통한 자동 감지
|
||||||
|
```python
|
||||||
|
def is_custom_prescription(compound_id):
|
||||||
|
"""조제가 원 처방과 다른지 확인"""
|
||||||
|
|
||||||
|
# 1. compound의 formula_id 확인
|
||||||
|
original_formula = get_formula_ingredients(formula_id)
|
||||||
|
|
||||||
|
# 2. 실제 사용된 약재 확인
|
||||||
|
actual_ingredients = get_compound_ingredients(compound_id)
|
||||||
|
|
||||||
|
# 3. 비교
|
||||||
|
if original_formula != actual_ingredients:
|
||||||
|
return True, get_differences()
|
||||||
|
|
||||||
|
return False, None
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 방법 2: 플래그 추가 (권장)
|
||||||
|
```sql
|
||||||
|
-- compounds 테이블에 컬럼 추가
|
||||||
|
ALTER TABLE compounds ADD COLUMN is_custom BOOLEAN DEFAULT 0;
|
||||||
|
ALTER TABLE compounds ADD COLUMN custom_notes TEXT;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 화면 표시 제안
|
||||||
|
|
||||||
|
#### 조제 내역 표시 예시
|
||||||
|
|
||||||
|
**원 처방 그대로 조제한 경우:**
|
||||||
|
```
|
||||||
|
조제일: 2024-02-17
|
||||||
|
처방: 십전대보탕
|
||||||
|
첩수: 20첩
|
||||||
|
```
|
||||||
|
|
||||||
|
**커스텀 조제한 경우:**
|
||||||
|
```
|
||||||
|
조제일: 2024-02-17
|
||||||
|
처방: 십전대보탕 (가감방) ⚠️
|
||||||
|
첩수: 20첩
|
||||||
|
추가: 구기자 3g
|
||||||
|
제외: 감초
|
||||||
|
변경: 인삼 5g → 7g
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 구현 제안사항
|
||||||
|
|
||||||
|
### 4.1 데이터베이스 개선
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 1. compounds 테이블에 커스텀 플래그 추가
|
||||||
|
ALTER TABLE compounds ADD COLUMN is_custom BOOLEAN DEFAULT 0;
|
||||||
|
ALTER TABLE compounds ADD COLUMN custom_type TEXT; -- 'added', 'removed', 'modified', 'mixed'
|
||||||
|
ALTER TABLE compounds ADD COLUMN custom_summary TEXT; -- "구기자 3g 추가"
|
||||||
|
|
||||||
|
-- 2. compound_ingredients에 변경 타입 추가
|
||||||
|
ALTER TABLE compound_ingredients ADD COLUMN modification_type TEXT; -- 'original', 'added', 'modified'
|
||||||
|
ALTER TABLE compound_ingredients ADD COLUMN original_grams REAL; -- 원래 용량 (수정된 경우)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 API 개선 제안
|
||||||
|
|
||||||
|
```python
|
||||||
|
@app.route('/api/compounds', methods=['POST'])
|
||||||
|
def create_compound():
|
||||||
|
"""조제 실행 - 커스텀 처방 감지 포함"""
|
||||||
|
|
||||||
|
data = request.json
|
||||||
|
formula_id = data.get('formula_id')
|
||||||
|
ingredients = data.get('ingredients')
|
||||||
|
|
||||||
|
# 원 처방과 비교
|
||||||
|
original = get_formula_ingredients(formula_id)
|
||||||
|
is_custom, differences = compare_ingredients(original, ingredients)
|
||||||
|
|
||||||
|
if is_custom:
|
||||||
|
# 커스텀 정보 저장
|
||||||
|
custom_summary = generate_custom_summary(differences)
|
||||||
|
# compounds 테이블에 is_custom=1, custom_summary 저장
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 UI 개선 제안
|
||||||
|
|
||||||
|
#### 조제 화면
|
||||||
|
```javascript
|
||||||
|
// 커스텀 여부 실시간 표시
|
||||||
|
function checkCustomization() {
|
||||||
|
const original = getOriginalFormula();
|
||||||
|
const current = getCurrentIngredients();
|
||||||
|
|
||||||
|
if (hasChanges(original, current)) {
|
||||||
|
$('#customBadge').show().html('가감방');
|
||||||
|
$('#customDetails').html(getChangesSummary());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 환자 처방 내역 화면
|
||||||
|
```javascript
|
||||||
|
// 커스텀 처방 구분 표시
|
||||||
|
function displayPrescriptionHistory(patient_id) {
|
||||||
|
// 처방 내역 표시 시
|
||||||
|
if (compound.is_custom) {
|
||||||
|
html += `<span class="badge bg-warning">가감</span>`;
|
||||||
|
html += `<small class="text-muted">${compound.custom_summary}</small>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 보고서 개선
|
||||||
|
|
||||||
|
환자 처방 내역서에 커스텀 정보 포함:
|
||||||
|
```
|
||||||
|
===========================================
|
||||||
|
환자명: 홍길동
|
||||||
|
기간: 2024-01-01 ~ 2024-02-17
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
1. 2024-01-15: 십전대보탕 (20첩)
|
||||||
|
- 표준 처방
|
||||||
|
|
||||||
|
2. 2024-02-01: 십전대보탕 가감방 (20첩)
|
||||||
|
- 추가: 구기자 3g/첩
|
||||||
|
- 제외: 감초
|
||||||
|
- 용량변경: 인삼 5g → 7g/첩
|
||||||
|
|
||||||
|
3. 2024-02-17: 쌍화탕 (15첩)
|
||||||
|
- 표준 처방
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 현재 시스템 활용 방안
|
||||||
|
|
||||||
|
현재 구조에서도 충분히 커스텀 처방을 관리할 수 있습니다:
|
||||||
|
|
||||||
|
1. **조제 시**: `compound_ingredients`에 실제 사용 약재 저장
|
||||||
|
2. **조회 시**: `formula_ingredients`와 비교하여 커스텀 여부 판단
|
||||||
|
3. **표시**: 차이점을 계산하여 화면에 표시
|
||||||
|
|
||||||
|
### 예시 쿼리
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 커스텀 처방 찾기
|
||||||
|
SELECT
|
||||||
|
c.compound_id,
|
||||||
|
c.formula_id,
|
||||||
|
f.formula_name,
|
||||||
|
CASE
|
||||||
|
WHEN ci_count != fi_count THEN '가감방'
|
||||||
|
ELSE '표준방'
|
||||||
|
END as prescription_type
|
||||||
|
FROM compounds c
|
||||||
|
JOIN formulas f ON c.formula_id = f.formula_id
|
||||||
|
JOIN (
|
||||||
|
-- 실제 사용 약재 수
|
||||||
|
SELECT compound_id, COUNT(*) as ci_count
|
||||||
|
FROM compound_ingredients
|
||||||
|
GROUP BY compound_id
|
||||||
|
) ci ON c.compound_id = ci.compound_id
|
||||||
|
LEFT JOIN (
|
||||||
|
-- 원 처방 약재 수
|
||||||
|
SELECT formula_id, COUNT(*) as fi_count
|
||||||
|
FROM formula_ingredients
|
||||||
|
GROUP BY formula_id
|
||||||
|
) fi ON c.formula_id = fi.formula_id;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 결론
|
||||||
|
|
||||||
|
현재 시스템은 이미 커스텀 처방을 저장할 수 있는 구조를 갖추고 있습니다:
|
||||||
|
- `formula_ingredients`: 원 처방 (불변)
|
||||||
|
- `compound_ingredients`: 실제 조제 (커스텀 가능)
|
||||||
|
|
||||||
|
추가 개선사항:
|
||||||
|
1. `compounds` 테이블에 `is_custom` 플래그 추가
|
||||||
|
2. 커스텀 내역을 요약하여 `custom_summary`에 저장
|
||||||
|
3. UI에서 가감방 표시 및 상세 내역 표시
|
||||||
|
4. 환자 처방 내역에 커스텀 정보 포함
|
||||||
|
|
||||||
|
이렇게 하면 "십전대보탕 + 구기자 3g"을 정확히 기록하고 추적할 수 있습니다.
|
||||||
172
migrations/add_custom_prescription_fields.py
Normal file
172
migrations/add_custom_prescription_fields.py
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
커스텀 처방 관리를 위한 데이터베이스 스키마 업데이트
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def get_connection():
|
||||||
|
"""데이터베이스 연결"""
|
||||||
|
return sqlite3.connect('database/kdrug.db')
|
||||||
|
|
||||||
|
def add_custom_fields():
|
||||||
|
"""커스텀 처방 관련 필드 추가"""
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print("커스텀 처방 관리를 위한 DB 스키마 업데이트")
|
||||||
|
print("="*60)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. compounds 테이블에 커스텀 관련 필드 추가
|
||||||
|
print("\n1. compounds 테이블 업데이트...")
|
||||||
|
|
||||||
|
# is_custom 컬럼 추가
|
||||||
|
cursor.execute("""
|
||||||
|
ALTER TABLE compounds
|
||||||
|
ADD COLUMN is_custom BOOLEAN DEFAULT 0
|
||||||
|
""")
|
||||||
|
print(" ✓ is_custom 컬럼 추가")
|
||||||
|
|
||||||
|
# custom_summary 컬럼 추가
|
||||||
|
cursor.execute("""
|
||||||
|
ALTER TABLE compounds
|
||||||
|
ADD COLUMN custom_summary TEXT
|
||||||
|
""")
|
||||||
|
print(" ✓ custom_summary 컬럼 추가")
|
||||||
|
|
||||||
|
# custom_type 컬럼 추가
|
||||||
|
cursor.execute("""
|
||||||
|
ALTER TABLE compounds
|
||||||
|
ADD COLUMN custom_type TEXT
|
||||||
|
""")
|
||||||
|
print(" ✓ custom_type 컬럼 추가")
|
||||||
|
|
||||||
|
except sqlite3.OperationalError as e:
|
||||||
|
if "duplicate column" in str(e):
|
||||||
|
print(" ⚠ 이미 컬럼이 존재합니다.")
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 2. compound_ingredients 테이블에 modification 관련 필드 추가
|
||||||
|
print("\n2. compound_ingredients 테이블 업데이트...")
|
||||||
|
|
||||||
|
# modification_type 컬럼 추가
|
||||||
|
cursor.execute("""
|
||||||
|
ALTER TABLE compound_ingredients
|
||||||
|
ADD COLUMN modification_type TEXT DEFAULT 'original'
|
||||||
|
""")
|
||||||
|
print(" ✓ modification_type 컬럼 추가")
|
||||||
|
|
||||||
|
# original_grams 컬럼 추가 (원래 용량 저장)
|
||||||
|
cursor.execute("""
|
||||||
|
ALTER TABLE compound_ingredients
|
||||||
|
ADD COLUMN original_grams REAL
|
||||||
|
""")
|
||||||
|
print(" ✓ original_grams 컬럼 추가")
|
||||||
|
|
||||||
|
except sqlite3.OperationalError as e:
|
||||||
|
if "duplicate column" in str(e):
|
||||||
|
print(" ⚠ 이미 컬럼이 존재합니다.")
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
# 3. 인덱스 추가
|
||||||
|
try:
|
||||||
|
print("\n3. 인덱스 생성...")
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_compounds_is_custom
|
||||||
|
ON compounds(is_custom)
|
||||||
|
""")
|
||||||
|
print(" ✓ is_custom 인덱스 생성")
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_compounds_patient_custom
|
||||||
|
ON compounds(patient_id, is_custom)
|
||||||
|
""")
|
||||||
|
print(" ✓ patient_id + is_custom 복합 인덱스 생성")
|
||||||
|
|
||||||
|
except sqlite3.OperationalError as e:
|
||||||
|
print(f" ⚠ 인덱스 생성 중 오류: {e}")
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
# 4. 스키마 확인
|
||||||
|
print("\n4. 업데이트된 스키마 확인...")
|
||||||
|
|
||||||
|
cursor.execute("PRAGMA table_info(compounds)")
|
||||||
|
columns = cursor.fetchall()
|
||||||
|
print("\n compounds 테이블 컬럼:")
|
||||||
|
for col in columns:
|
||||||
|
if col[1] in ['is_custom', 'custom_summary', 'custom_type']:
|
||||||
|
print(f" ✓ {col[1]:20s} {col[2]}")
|
||||||
|
|
||||||
|
cursor.execute("PRAGMA table_info(compound_ingredients)")
|
||||||
|
columns = cursor.fetchall()
|
||||||
|
print("\n compound_ingredients 테이블 컬럼:")
|
||||||
|
for col in columns:
|
||||||
|
if col[1] in ['modification_type', 'original_grams']:
|
||||||
|
print(f" ✓ {col[1]:20s} {col[2]}")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print("✅ DB 스키마 업데이트 완료!")
|
||||||
|
print("="*60)
|
||||||
|
|
||||||
|
def test_custom_fields():
|
||||||
|
"""업데이트된 필드 테스트"""
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
print("\n테스트: 커스텀 필드 동작 확인...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 테스트 쿼리
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT
|
||||||
|
compound_id,
|
||||||
|
is_custom,
|
||||||
|
custom_summary,
|
||||||
|
custom_type
|
||||||
|
FROM compounds
|
||||||
|
LIMIT 1
|
||||||
|
""")
|
||||||
|
|
||||||
|
result = cursor.fetchone()
|
||||||
|
if result:
|
||||||
|
print(" ✓ compounds 테이블 커스텀 필드 정상")
|
||||||
|
else:
|
||||||
|
print(" ℹ compounds 테이블이 비어있습니다.")
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT
|
||||||
|
compound_ingredient_id,
|
||||||
|
modification_type,
|
||||||
|
original_grams
|
||||||
|
FROM compound_ingredients
|
||||||
|
LIMIT 1
|
||||||
|
""")
|
||||||
|
|
||||||
|
result = cursor.fetchone()
|
||||||
|
if result:
|
||||||
|
print(" ✓ compound_ingredients 테이블 커스텀 필드 정상")
|
||||||
|
else:
|
||||||
|
print(" ℹ compound_ingredients 테이블이 비어있습니다.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ 테스트 실패: {e}")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""메인 실행"""
|
||||||
|
add_custom_fields()
|
||||||
|
test_custom_fields()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
103
static/app.js
103
static/app.js
@ -1,5 +1,8 @@
|
|||||||
// 한약 재고관리 시스템 - Frontend JavaScript
|
// 한약 재고관리 시스템 - Frontend JavaScript
|
||||||
|
|
||||||
|
// 원래 처방 구성 저장용 전역 변수
|
||||||
|
let originalFormulaIngredients = {};
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
// 페이지 네비게이션
|
// 페이지 네비게이션
|
||||||
$('.sidebar .nav-link').on('click', function(e) {
|
$('.sidebar .nav-link').on('click', function(e) {
|
||||||
@ -273,13 +276,22 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
const detailRowId = `compound-detail-${compound.compound_id}`;
|
const detailRowId = `compound-detail-${compound.compound_id}`;
|
||||||
|
|
||||||
|
// 처방명 표시 (가감방 여부 포함)
|
||||||
|
let formulaDisplay = compound.formula_name || '직접조제';
|
||||||
|
if (compound.is_custom && compound.formula_name) {
|
||||||
|
formulaDisplay = `${compound.formula_name} <span class="badge bg-warning ms-1">가감</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
tbody.append(`
|
tbody.append(`
|
||||||
<tr class="compound-row" style="cursor: pointer;" data-compound-id="${compound.compound_id}">
|
<tr class="compound-row" style="cursor: pointer;" data-compound-id="${compound.compound_id}">
|
||||||
<td>
|
<td>
|
||||||
<i class="bi bi-chevron-right toggle-icon"></i>
|
<i class="bi bi-chevron-right toggle-icon"></i>
|
||||||
</td>
|
</td>
|
||||||
<td>${compound.compound_date || '-'}</td>
|
<td>${compound.compound_date || '-'}</td>
|
||||||
<td>${compound.formula_name || '직접조제'}</td>
|
<td>
|
||||||
|
${formulaDisplay}
|
||||||
|
${compound.custom_summary ? `<br><small class="text-muted">${compound.custom_summary}</small>` : ''}
|
||||||
|
</td>
|
||||||
<td>${compound.je_count || 0}</td>
|
<td>${compound.je_count || 0}</td>
|
||||||
<td>${compound.cheop_total || 0}</td>
|
<td>${compound.cheop_total || 0}</td>
|
||||||
<td>${compound.pouch_total || 0}</td>
|
<td>${compound.pouch_total || 0}</td>
|
||||||
@ -573,6 +585,11 @@ $(document).ready(function() {
|
|||||||
// 처방 선택 시 구성 약재 로드
|
// 처방 선택 시 구성 약재 로드
|
||||||
$('#compoundFormula').on('change', function() {
|
$('#compoundFormula').on('change', function() {
|
||||||
const formulaId = $(this).val();
|
const formulaId = $(this).val();
|
||||||
|
|
||||||
|
// 원래 처방 구성 초기화
|
||||||
|
originalFormulaIngredients = {};
|
||||||
|
$('#customPrescriptionBadge').remove(); // 커스텀 뱃지 제거
|
||||||
|
|
||||||
if (!formulaId) {
|
if (!formulaId) {
|
||||||
$('#compoundIngredients').empty();
|
$('#compoundIngredients').empty();
|
||||||
return;
|
return;
|
||||||
@ -591,6 +608,14 @@ $(document).ready(function() {
|
|||||||
if (response.success) {
|
if (response.success) {
|
||||||
$('#compoundIngredients').empty();
|
$('#compoundIngredients').empty();
|
||||||
|
|
||||||
|
// 원래 처방 구성 저장
|
||||||
|
response.data.forEach(ing => {
|
||||||
|
originalFormulaIngredients[ing.ingredient_code] = {
|
||||||
|
herb_name: ing.herb_name,
|
||||||
|
grams_per_cheop: ing.grams_per_cheop
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
response.data.forEach(ing => {
|
response.data.forEach(ing => {
|
||||||
const cheopTotal = parseFloat($('#cheopTotal').val()) || 0;
|
const cheopTotal = parseFloat($('#cheopTotal').val()) || 0;
|
||||||
const totalGrams = ing.grams_per_cheop * cheopTotal;
|
const totalGrams = ing.grams_per_cheop * cheopTotal;
|
||||||
@ -688,6 +713,7 @@ $(document).ready(function() {
|
|||||||
// 삭제 버튼 이벤트
|
// 삭제 버튼 이벤트
|
||||||
$('.remove-compound-ingredient').on('click', function() {
|
$('.remove-compound-ingredient').on('click', function() {
|
||||||
$(this).closest('tr').remove();
|
$(this).closest('tr').remove();
|
||||||
|
updateIngredientTotals(); // 총용량 재계산 및 커스텀 감지
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -704,6 +730,80 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
checkStockForCompound();
|
checkStockForCompound();
|
||||||
|
// 커스텀 처방 감지 호출
|
||||||
|
checkCustomPrescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 커스텀 처방 감지 함수
|
||||||
|
function checkCustomPrescription() {
|
||||||
|
const formulaId = $('#compoundFormula').val();
|
||||||
|
|
||||||
|
// 처방이 선택되지 않았거나 직접조제인 경우 리턴
|
||||||
|
if (!formulaId || formulaId === 'custom' || Object.keys(originalFormulaIngredients).length === 0) {
|
||||||
|
$('#customPrescriptionBadge').remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 현재 약재 구성 수집
|
||||||
|
const currentIngredients = {};
|
||||||
|
$('#compoundIngredients tr').each(function() {
|
||||||
|
const ingredientCode = $(this).attr('data-ingredient-code');
|
||||||
|
const gramsPerCheop = parseFloat($(this).find('.grams-per-cheop').val());
|
||||||
|
|
||||||
|
if (ingredientCode && gramsPerCheop > 0) {
|
||||||
|
currentIngredients[ingredientCode] = gramsPerCheop;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 변경사항 감지
|
||||||
|
const customDetails = [];
|
||||||
|
let isCustom = false;
|
||||||
|
|
||||||
|
// 추가된 약재 확인
|
||||||
|
for (const code in currentIngredients) {
|
||||||
|
if (!originalFormulaIngredients[code]) {
|
||||||
|
const herbName = $(`tr[data-ingredient-code="${code}"] .herb-select-compound option:selected`).data('herb-name') || code;
|
||||||
|
customDetails.push(`${herbName} ${currentIngredients[code]}g 추가`);
|
||||||
|
isCustom = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 삭제된 약재 확인
|
||||||
|
for (const code in originalFormulaIngredients) {
|
||||||
|
if (!currentIngredients[code]) {
|
||||||
|
customDetails.push(`${originalFormulaIngredients[code].herb_name} 제거`);
|
||||||
|
isCustom = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 용량 변경된 약재 확인
|
||||||
|
for (const code in currentIngredients) {
|
||||||
|
if (originalFormulaIngredients[code]) {
|
||||||
|
const originalGrams = originalFormulaIngredients[code].grams_per_cheop;
|
||||||
|
const currentGrams = currentIngredients[code];
|
||||||
|
|
||||||
|
if (Math.abs(originalGrams - currentGrams) > 0.01) {
|
||||||
|
const herbName = originalFormulaIngredients[code].herb_name;
|
||||||
|
customDetails.push(`${herbName} ${originalGrams}g→${currentGrams}g`);
|
||||||
|
isCustom = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 커스텀 뱃지 표시/숨기기
|
||||||
|
$('#customPrescriptionBadge').remove();
|
||||||
|
|
||||||
|
if (isCustom) {
|
||||||
|
const badgeHtml = `
|
||||||
|
<div id="customPrescriptionBadge" class="alert alert-warning mt-2">
|
||||||
|
<span class="badge bg-warning">가감방</span>
|
||||||
|
<small class="ms-2">${customDetails.join(' | ')}</small>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 처방 선택 영역 아래에 추가
|
||||||
|
$('#compoundFormula').closest('.col-md-6').append(badgeHtml);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 재고 확인
|
// 재고 확인
|
||||||
@ -861,6 +961,7 @@ $(document).ready(function() {
|
|||||||
newRow.find('.grams-per-cheop').on('input', updateIngredientTotals);
|
newRow.find('.grams-per-cheop').on('input', updateIngredientTotals);
|
||||||
newRow.find('.remove-compound-ingredient').on('click', function() {
|
newRow.find('.remove-compound-ingredient').on('click', function() {
|
||||||
$(this).closest('tr').remove();
|
$(this).closest('tr').remove();
|
||||||
|
updateIngredientTotals(); // 총용량 재계산 및 커스텀 감지
|
||||||
});
|
});
|
||||||
newRow.find('.herb-select-compound').on('change', function() {
|
newRow.find('.herb-select-compound').on('change', function() {
|
||||||
const herbId = $(this).val();
|
const herbId = $(this).val();
|
||||||
|
|||||||
128
test_custom_prescription.html
Normal file
128
test_custom_prescription.html
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>커스텀 처방 실시간 감지 테스트</title>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<style>
|
||||||
|
body { font-family: sans-serif; margin: 20px; }
|
||||||
|
.container { max-width: 800px; margin: auto; }
|
||||||
|
.badge { padding: 2px 8px; border-radius: 4px; color: white; }
|
||||||
|
.bg-warning { background-color: #ffc107; color: #333; }
|
||||||
|
.alert { padding: 10px; margin-top: 10px; border-radius: 4px; }
|
||||||
|
.alert-warning { background-color: #fff3cd; border: 1px solid #ffeaa7; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>커스텀 처방 감지 테스트</h1>
|
||||||
|
|
||||||
|
<div style="margin: 20px 0;">
|
||||||
|
<h3>시나리오:</h3>
|
||||||
|
<p>십전대보탕을 선택한 후, 약재를 추가/삭제/변경하면 "가감방" 표시가 나타납니다.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="testResult" style="margin-top: 20px; padding: 20px; border: 1px solid #ddd;">
|
||||||
|
<h3>테스트 결과:</h3>
|
||||||
|
<div id="resultContent">테스트를 시작하려면 페이지를 새로고침하세요.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 원래 처방 구성 저장용 변수
|
||||||
|
let originalFormulaIngredients = {};
|
||||||
|
|
||||||
|
// 테스트 시나리오
|
||||||
|
function runTest() {
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
// 1. 십전대보탕 원래 구성 저장
|
||||||
|
originalFormulaIngredients = {
|
||||||
|
'3052A12AM': { herb_name: '감초', grams_per_cheop: 1.5 },
|
||||||
|
'3047A10AM': { herb_name: '당귀', grams_per_cheop: 3.0 },
|
||||||
|
'3065A10AM': { herb_name: '백출', grams_per_cheop: 3.0 },
|
||||||
|
'3064B19AM': { herb_name: '백작약', grams_per_cheop: 3.0 },
|
||||||
|
'3073B11AM': { herb_name: '숙지황', grams_per_cheop: 3.0 },
|
||||||
|
'3054A14AM': { herb_name: '인삼', grams_per_cheop: 3.0 },
|
||||||
|
'3072A17AM': { herb_name: '천궁', grams_per_cheop: 3.0 },
|
||||||
|
'3056A18AM': { herb_name: '황기', grams_per_cheop: 3.0 },
|
||||||
|
'3063A18AM': { herb_name: '백복령', grams_per_cheop: 3.0 },
|
||||||
|
'3055A13AM': { herb_name: '육계', grams_per_cheop: 1.5 }
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. 현재 약재 구성 (구기자 추가)
|
||||||
|
const currentIngredients = {
|
||||||
|
'3052A12AM': 1.5, // 감초
|
||||||
|
'3047A10AM': 3.0, // 당귀
|
||||||
|
'3065A10AM': 3.0, // 백출
|
||||||
|
'3064B19AM': 3.0, // 백작약
|
||||||
|
'3073B11AM': 3.0, // 숙지황
|
||||||
|
'3054A14AM': 3.0, // 인삼
|
||||||
|
'3072A17AM': 3.0, // 천궁
|
||||||
|
'3056A18AM': 3.0, // 황기
|
||||||
|
'3063A18AM': 3.0, // 백복령
|
||||||
|
'3055A13AM': 1.5, // 육계
|
||||||
|
'3147H1AHM': 3.0 // 구기자 (추가)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3. 커스텀 감지 로직
|
||||||
|
const customDetails = [];
|
||||||
|
let isCustom = false;
|
||||||
|
|
||||||
|
// 추가된 약재 확인
|
||||||
|
for (const code in currentIngredients) {
|
||||||
|
if (!originalFormulaIngredients[code]) {
|
||||||
|
customDetails.push(`구기자 ${currentIngredients[code]}g 추가`);
|
||||||
|
isCustom = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 삭제된 약재 확인
|
||||||
|
for (const code in originalFormulaIngredients) {
|
||||||
|
if (!currentIngredients[code]) {
|
||||||
|
customDetails.push(`${originalFormulaIngredients[code].herb_name} 제거`);
|
||||||
|
isCustom = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 용량 변경된 약재 확인
|
||||||
|
for (const code in currentIngredients) {
|
||||||
|
if (originalFormulaIngredients[code]) {
|
||||||
|
const originalGrams = originalFormulaIngredients[code].grams_per_cheop;
|
||||||
|
const currentGrams = currentIngredients[code];
|
||||||
|
|
||||||
|
if (Math.abs(originalGrams - currentGrams) > 0.01) {
|
||||||
|
const herbName = originalFormulaIngredients[code].herb_name;
|
||||||
|
customDetails.push(`${herbName} ${originalGrams}g→${currentGrams}g`);
|
||||||
|
isCustom = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 결과 표시
|
||||||
|
if (isCustom) {
|
||||||
|
const badgeHtml = `
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<span class="badge bg-warning">가감방</span>
|
||||||
|
<small style="margin-left: 10px;">${customDetails.join(' | ')}</small>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
$('#resultContent').html(`
|
||||||
|
<p><strong>✅ 테스트 성공!</strong></p>
|
||||||
|
<p>십전대보탕에 구기자 3g를 추가한 경우:</p>
|
||||||
|
${badgeHtml}
|
||||||
|
<p style="margin-top: 10px;">커스텀 처방이 정상적으로 감지되었습니다.</p>
|
||||||
|
`);
|
||||||
|
} else {
|
||||||
|
$('#resultContent').html('<p>❌ 커스텀 처방이 감지되지 않았습니다.</p>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 페이지 로드 시 테스트 실행
|
||||||
|
$(document).ready(function() {
|
||||||
|
runTest();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue
Block a user