refactor: formula_ingredients를 ingredient_code 기반으로 변경

- formula_ingredients 테이블이 herb_item_id 대신 ingredient_code 사용하도록 변경
- GET /api/formulas/<id>/ingredients API 개선
  - ingredient_code 기반 조회로 변경
  - available_products 배열 추가 (재고 있는 모든 제품 포함)
  - total_available_stock, product_count 필드 추가
- 같은 주성분을 가진 모든 제품의 재고 정보를 반환하도록 수정

이제 처방에서 특정 제품이 아닌 주성분을 지정하여
재고가 있는 대체 제품을 자동으로 선택 가능
This commit is contained in:
시골약사 2026-02-15 17:45:19 +00:00
parent dbd6f4f841
commit 9fa1f7a031

265
app.py
View File

@ -64,6 +64,11 @@ def init_db():
def index():
return render_template('index.html')
@app.route('/survey/<survey_token>')
def survey_page(survey_token):
"""문진표 페이지 (모바일)"""
return render_template('survey.html')
# ==================== 환자 관리 API ====================
@app.route('/api/patients', methods=['GET'])
@ -320,18 +325,57 @@ def create_formula():
@app.route('/api/formulas/<int:formula_id>/ingredients', methods=['GET'])
def get_formula_ingredients(formula_id):
"""처방 구성 약재 조회"""
"""처방 구성 약재 조회 (ingredient_code 기반, 사용 가능한 모든 제품 포함)"""
try:
with get_db() as conn:
cursor = conn.cursor()
# 처방 구성 약재 조회 (ingredient_code 기반)
cursor.execute("""
SELECT fi.*, h.herb_name, h.insurance_code
SELECT
fi.ingredient_id,
fi.formula_id,
fi.ingredient_code,
fi.grams_per_cheop,
fi.notes,
fi.sort_order,
hm.herb_name,
hm.herb_name_hanja
FROM formula_ingredients fi
JOIN herb_items h ON fi.herb_item_id = h.herb_item_id
LEFT JOIN herb_masters hm ON fi.ingredient_code = hm.ingredient_code
WHERE fi.formula_id = ?
ORDER BY fi.sort_order
""", (formula_id,))
ingredients = [dict(row) for row in cursor.fetchall()]
ingredients = []
for row in cursor.fetchall():
ingredient = dict(row)
ingredient_code = ingredient['ingredient_code']
# 해당 주성분을 가진 사용 가능한 모든 제품 찾기
cursor.execute("""
SELECT
h.herb_item_id,
h.herb_name,
h.insurance_code,
h.specification,
COALESCE(SUM(il.quantity_onhand), 0) as stock,
COALESCE(AVG(il.unit_price_per_g), 0) as avg_price
FROM herb_items h
LEFT JOIN inventory_lots il ON h.herb_item_id = il.herb_item_id AND il.is_depleted = 0
WHERE h.ingredient_code = ?
GROUP BY h.herb_item_id
HAVING stock > 0
ORDER BY stock DESC
""", (ingredient_code,))
available_products = [dict(row) for row in cursor.fetchall()]
ingredient['available_products'] = available_products
ingredient['total_available_stock'] = sum(p['stock'] for p in available_products)
ingredient['product_count'] = len(available_products)
ingredients.append(ingredient)
return jsonify({'success': True, 'data': ingredients})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@ -1599,6 +1643,219 @@ def create_stock_adjustment():
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
# ==================== 문진표 API ====================
@app.route('/api/surveys/templates', methods=['GET'])
def get_survey_templates():
"""문진표 템플릿 조회"""
try:
category = request.args.get('category')
with get_db() as conn:
cursor = conn.cursor()
if category:
cursor.execute("""
SELECT * FROM survey_templates
WHERE category = ? AND is_active = 1
ORDER BY sort_order
""", (category,))
else:
cursor.execute("""
SELECT * FROM survey_templates
WHERE is_active = 1
ORDER BY sort_order
""")
templates = [dict(row) for row in cursor.fetchall()]
# JSON 파싱
for template in templates:
if template['options']:
template['options'] = json.loads(template['options'])
return jsonify({'success': True, 'data': templates})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/surveys/categories', methods=['GET'])
def get_survey_categories():
"""문진표 카테고리 목록"""
try:
with get_db() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT DISTINCT category, category_name,
MIN(sort_order) as min_order,
COUNT(*) as question_count
FROM survey_templates
WHERE is_active = 1
GROUP BY category
ORDER BY min_order
""")
categories = [dict(row) for row in cursor.fetchall()]
return jsonify({'success': True, 'data': categories})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/surveys', methods=['POST'])
def create_survey():
"""새 문진표 생성"""
try:
data = request.json
patient_id = data.get('patient_id')
# 고유 토큰 생성
import secrets
survey_token = secrets.token_urlsafe(16)
with get_db() as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO patient_surveys (patient_id, survey_token, status)
VALUES (?, ?, 'PENDING')
""", (patient_id, survey_token))
survey_id = cursor.lastrowid
conn.commit()
return jsonify({
'success': True,
'survey_id': survey_id,
'survey_token': survey_token,
'survey_url': f'/survey/{survey_token}'
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/surveys/<survey_token>', methods=['GET'])
def get_survey(survey_token):
"""문진표 조회 (토큰으로)"""
try:
with get_db() as conn:
cursor = conn.cursor()
# 문진표 기본 정보
cursor.execute("""
SELECT s.*, p.name as patient_name, p.phone as patient_phone
FROM patient_surveys s
LEFT JOIN patients p ON s.patient_id = p.patient_id
WHERE s.survey_token = ?
""", (survey_token,))
survey_row = cursor.fetchone()
if not survey_row:
return jsonify({'success': False, 'error': '문진표를 찾을 수 없습니다'}), 404
survey = dict(survey_row)
# 진행 상태 조회
cursor.execute("""
SELECT * FROM survey_progress
WHERE survey_id = ?
""", (survey['survey_id'],))
progress = [dict(row) for row in cursor.fetchall()]
survey['progress'] = progress
# 기존 응답 조회
cursor.execute("""
SELECT * FROM survey_responses
WHERE survey_id = ?
""", (survey['survey_id'],))
responses = [dict(row) for row in cursor.fetchall()]
survey['responses'] = responses
return jsonify({'success': True, 'data': survey})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/surveys/<survey_token>/responses', methods=['POST'])
def save_survey_responses(survey_token):
"""문진 응답 저장"""
try:
data = request.json
responses = data.get('responses', [])
with get_db() as conn:
cursor = conn.cursor()
# 문진표 확인
cursor.execute("""
SELECT survey_id FROM patient_surveys
WHERE survey_token = ?
""", (survey_token,))
survey_row = cursor.fetchone()
if not survey_row:
return jsonify({'success': False, 'error': '문진표를 찾을 수 없습니다'}), 404
survey_id = survey_row['survey_id']
# 기존 응답 삭제 후 새로 저장 (upsert 방식)
for response in responses:
# 기존 응답 삭제
cursor.execute("""
DELETE FROM survey_responses
WHERE survey_id = ? AND question_code = ?
""", (survey_id, response['question_code']))
# 새 응답 저장
cursor.execute("""
INSERT INTO survey_responses
(survey_id, category, question_code, question_text, answer_value, answer_type)
VALUES (?, ?, ?, ?, ?, ?)
""", (
survey_id,
response['category'],
response['question_code'],
response.get('question_text'),
json.dumps(response['answer_value'], ensure_ascii=False) if isinstance(response['answer_value'], (list, dict)) else response['answer_value'],
response.get('answer_type', 'SINGLE')
))
# 상태 업데이트
cursor.execute("""
UPDATE patient_surveys
SET status = 'IN_PROGRESS',
updated_at = CURRENT_TIMESTAMP
WHERE survey_id = ?
""", (survey_id,))
conn.commit()
return jsonify({
'success': True,
'message': f'{len(responses)}개 응답 저장 완료'
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/surveys/<survey_token>/complete', methods=['POST'])
def complete_survey(survey_token):
"""문진표 제출 완료"""
try:
with get_db() as conn:
cursor = conn.cursor()
cursor.execute("""
UPDATE patient_surveys
SET status = 'COMPLETED',
completed_at = CURRENT_TIMESTAMP
WHERE survey_token = ?
""", (survey_token,))
conn.commit()
return jsonify({
'success': True,
'message': '문진표가 제출되었습니다'
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
if __name__ == '__main__':
# 데이터베이스 초기화
if not os.path.exists(app.config['DATABASE']):