feat: OTC 용법 라벨 시스템 구현
DB: - otc_label_presets 테이블 추가 (SQLite) - 바코드 기준 오버라이드 데이터 저장 Backend: - utils/otc_label_printer.py: 라벨 이미지 생성 + Brother QL-810W 출력 - API: CRUD + 미리보기 렌더링 + MSSQL 약품 검색 Frontend: - /admin/otc-labels: 관리 페이지 - 실시간 미리보기 - 저장된 프리셋 목록 - 바코드/이름 검색 → 프리셋 편집 → 인쇄
This commit is contained in:
261
backend/app.py
261
backend/app.py
@@ -5375,6 +5375,267 @@ def api_admin_qr_print():
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# OTC 용법 라벨 시스템 API
|
||||
# ============================================================================
|
||||
|
||||
@app.route('/admin/otc-labels')
|
||||
def admin_otc_labels():
|
||||
"""OTC 용법 라벨 관리 페이지"""
|
||||
return render_template('admin_otc_labels.html')
|
||||
|
||||
|
||||
@app.route('/api/admin/otc-labels', methods=['GET'])
|
||||
def api_get_otc_labels():
|
||||
"""OTC 라벨 프리셋 목록 조회"""
|
||||
try:
|
||||
conn = db_manager.get_sqlite_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT id, barcode, drug_code, display_name, effect,
|
||||
dosage_instruction, usage_tip, use_wide_format,
|
||||
print_count, last_printed_at, created_at, updated_at
|
||||
FROM otc_label_presets
|
||||
ORDER BY updated_at DESC
|
||||
""")
|
||||
|
||||
rows = cursor.fetchall()
|
||||
labels = [dict(row) for row in rows]
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'count': len(labels),
|
||||
'labels': labels
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"OTC 라벨 목록 조회 오류: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/admin/otc-labels/<barcode>', methods=['GET'])
|
||||
def api_get_otc_label(barcode):
|
||||
"""OTC 라벨 프리셋 단건 조회 (바코드 기준)"""
|
||||
try:
|
||||
conn = db_manager.get_sqlite_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT id, barcode, drug_code, display_name, effect,
|
||||
dosage_instruction, usage_tip, use_wide_format,
|
||||
print_count, last_printed_at, created_at, updated_at
|
||||
FROM otc_label_presets
|
||||
WHERE barcode = ?
|
||||
""", (barcode,))
|
||||
|
||||
row = cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return jsonify({'success': False, 'error': '등록된 프리셋이 없습니다.', 'exists': False}), 404
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'exists': True,
|
||||
'label': dict(row)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"OTC 라벨 조회 오류: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/admin/otc-labels', methods=['POST'])
|
||||
def api_upsert_otc_label():
|
||||
"""OTC 라벨 프리셋 등록/수정 (Upsert)"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
|
||||
if not data or not data.get('barcode'):
|
||||
return jsonify({'success': False, 'error': 'barcode는 필수입니다.'}), 400
|
||||
|
||||
barcode = data['barcode']
|
||||
drug_code = data.get('drug_code', '')
|
||||
display_name = data.get('display_name', '')
|
||||
effect = data.get('effect', '')
|
||||
dosage_instruction = data.get('dosage_instruction', '')
|
||||
usage_tip = data.get('usage_tip', '')
|
||||
use_wide_format = data.get('use_wide_format', True)
|
||||
|
||||
conn = db_manager.get_sqlite_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Upsert (INSERT OR REPLACE)
|
||||
cursor.execute("""
|
||||
INSERT INTO otc_label_presets
|
||||
(barcode, drug_code, display_name, effect, dosage_instruction, usage_tip, use_wide_format, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT(barcode) DO UPDATE SET
|
||||
drug_code = excluded.drug_code,
|
||||
display_name = excluded.display_name,
|
||||
effect = excluded.effect,
|
||||
dosage_instruction = excluded.dosage_instruction,
|
||||
usage_tip = excluded.usage_tip,
|
||||
use_wide_format = excluded.use_wide_format,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
""", (barcode, drug_code, display_name, effect, dosage_instruction, usage_tip, use_wide_format))
|
||||
|
||||
conn.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'라벨 프리셋 저장 완료 ({barcode})'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"OTC 라벨 저장 오류: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/admin/otc-labels/<barcode>', methods=['DELETE'])
|
||||
def api_delete_otc_label(barcode):
|
||||
"""OTC 라벨 프리셋 삭제"""
|
||||
try:
|
||||
conn = db_manager.get_sqlite_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("DELETE FROM otc_label_presets WHERE barcode = ?", (barcode,))
|
||||
conn.commit()
|
||||
|
||||
if cursor.rowcount > 0:
|
||||
return jsonify({'success': True, 'message': f'삭제 완료 ({barcode})'})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': '존재하지 않는 프리셋'}), 404
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"OTC 라벨 삭제 오류: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/admin/otc-labels/preview', methods=['POST'])
|
||||
def api_preview_otc_label():
|
||||
"""OTC 라벨 미리보기 이미지 생성"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
|
||||
if not data:
|
||||
return jsonify({'success': False, 'error': '요청 데이터가 없습니다.'}), 400
|
||||
|
||||
drug_name = data.get('drug_name', '약품명')
|
||||
effect = data.get('effect', '')
|
||||
dosage_instruction = data.get('dosage_instruction', '')
|
||||
usage_tip = data.get('usage_tip', '')
|
||||
|
||||
from utils.otc_label_printer import generate_preview_image
|
||||
|
||||
preview_url = generate_preview_image(drug_name, effect, dosage_instruction, usage_tip)
|
||||
|
||||
if preview_url:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'preview_url': preview_url
|
||||
})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': '미리보기 생성 실패'}), 500
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"OTC 라벨 미리보기 오류: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/admin/otc-labels/print', methods=['POST'])
|
||||
def api_print_otc_label():
|
||||
"""OTC 라벨 인쇄 (Brother QL-810W)"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
|
||||
if not data:
|
||||
return jsonify({'success': False, 'error': '요청 데이터가 없습니다.'}), 400
|
||||
|
||||
barcode = data.get('barcode')
|
||||
drug_name = data.get('drug_name', '약품명')
|
||||
effect = data.get('effect', '')
|
||||
dosage_instruction = data.get('dosage_instruction', '')
|
||||
usage_tip = data.get('usage_tip', '')
|
||||
|
||||
from utils.otc_label_printer import print_otc_label
|
||||
|
||||
success = print_otc_label(drug_name, effect, dosage_instruction, usage_tip)
|
||||
|
||||
if success:
|
||||
# 인쇄 횟수 업데이트
|
||||
if barcode:
|
||||
conn = db_manager.get_sqlite_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
UPDATE otc_label_presets
|
||||
SET print_count = print_count + 1, last_printed_at = CURRENT_TIMESTAMP
|
||||
WHERE barcode = ?
|
||||
""", (barcode,))
|
||||
conn.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'라벨 인쇄 완료: {drug_name}'
|
||||
})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': '프린터 전송 실패'}), 500
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"OTC 라벨 인쇄 오류: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/admin/otc-labels/search-mssql', methods=['GET'])
|
||||
def api_search_mssql_drug():
|
||||
"""MSSQL에서 약품 검색 (바코드 또는 이름)"""
|
||||
try:
|
||||
query = request.args.get('q', '').strip()
|
||||
|
||||
if not query:
|
||||
return jsonify({'success': False, 'error': '검색어를 입력해주세요.'}), 400
|
||||
|
||||
mssql_session = db_manager.get_session('PM_DRUG')
|
||||
|
||||
# 바코드 또는 이름으로 검색
|
||||
sql = text("""
|
||||
SELECT TOP 20
|
||||
DrugCode, Barcode, GoodsName, Saleprice, StockQty
|
||||
FROM CD_GOODS
|
||||
WHERE (Barcode LIKE :query OR GoodsName LIKE :query)
|
||||
AND Barcode IS NOT NULL
|
||||
AND Barcode != ''
|
||||
ORDER BY
|
||||
CASE WHEN Barcode = :exact THEN 0 ELSE 1 END,
|
||||
GoodsName
|
||||
""")
|
||||
|
||||
rows = mssql_session.execute(sql, {
|
||||
'query': f'%{query}%',
|
||||
'exact': query
|
||||
}).fetchall()
|
||||
|
||||
drugs = []
|
||||
for row in rows:
|
||||
drugs.append({
|
||||
'drug_code': row.DrugCode,
|
||||
'barcode': row.Barcode,
|
||||
'goods_name': row.GoodsName,
|
||||
'sale_price': float(row.Saleprice or 0),
|
||||
'stock_qty': int(row.StockQty or 0)
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'count': len(drugs),
|
||||
'drugs': drugs
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"MSSQL 약품 검색 오류: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import os
|
||||
|
||||
|
||||
Reference in New Issue
Block a user