diff --git a/backend/pmr_api.py b/backend/pmr_api.py index be06c7d..ad6b9ca 100644 --- a/backend/pmr_api.py +++ b/backend/pmr_api.py @@ -36,6 +36,10 @@ from pathlib import Path from datetime import datetime, date import logging from PIL import Image, ImageDraw, ImageFont + +# Pillow 10+ 호환성 패치 (brother_ql용) +if not hasattr(Image, 'ANTIALIAS'): + Image.ANTIALIAS = Image.Resampling.LANCZOS import io import base64 import os @@ -624,6 +628,101 @@ def preview_label(): return jsonify({'success': False, 'error': str(e)}), 500 +# API: 라벨 인쇄 (Brother QL 프린터) +# ───────────────────────────────────────────────────────────── +@pmr_bp.route('/api/label/print', methods=['POST']) +def print_label(): + """ + 라벨 인쇄 (PIL 렌더링 → Brother QL 프린터 전송) + + Request Body: + - patient_name, med_name, dosage, frequency, duration, unit, sung_code + - printer: 프린터 선택 (선택, 기본값 '168') + - '121': QL-710W (192.168.0.121) + - '168': QL-810W (192.168.0.168) + """ + try: + from brother_ql.raster import BrotherQLRaster + from brother_ql.conversion import convert + from brother_ql.backends.helpers import send + + data = request.get_json() + + patient_name = data.get('patient_name', '') + med_name = data.get('med_name', '') + add_info = data.get('add_info', '') + dosage = float(data.get('dosage', 0)) + frequency = int(data.get('frequency', 0)) + duration = int(data.get('duration', 0)) + unit = data.get('unit', '정') + sung_code = data.get('sung_code', '') + printer = data.get('printer', '168') # 기본값: QL-810W + + # 프린터 설정 + if printer == '121': + printer_ip = '192.168.0.121' + printer_model = 'QL-710W' + else: + printer_ip = '192.168.0.168' + printer_model = 'QL-810W' + + # 환산계수 조회 + conversion_factor = None + if sung_code: + try: + from db.dbsetup import db_manager + cf_result = db_manager.get_conversion_factor(sung_code) + conversion_factor = cf_result.get('conversion_factor') + except Exception as cf_err: + logging.warning(f"환산계수 조회 실패 (무시): {cf_err}") + + # 1. 라벨 이미지 생성 + label_image = create_label_image( + patient_name=patient_name, + med_name=med_name, + add_info=add_info, + dosage=dosage, + frequency=frequency, + duration=duration, + unit=unit, + conversion_factor=conversion_factor + ) + + # 2. 이미지 90도 회전 (Brother QL이 세로 방향 기준이므로) + label_rotated = label_image.rotate(90, expand=True) + + # 3. Brother QL 프린터로 전송 + qlr = BrotherQLRaster(printer_model) + instructions = convert( + qlr=qlr, + images=[label_rotated], + label='29', + rotate='0', + threshold=70.0, + dither=False, + compress=False, + red=False, + dpi_600=False, + hq=True, + cut=True + ) + send(instructions, printer_identifier=f"tcp://{printer_ip}:9100") + + logging.info(f"[SUCCESS] PMR 라벨 인쇄 성공: {med_name} → {printer_model}") + return jsonify({ + 'success': True, + 'message': f'{med_name} 라벨 인쇄 완료 ({printer_model})', + 'printer': printer_model + }) + + except ImportError as e: + logging.error(f"brother_ql 라이브러리 없음: {e}") + return jsonify({'success': False, 'error': 'brother_ql 라이브러리가 설치되지 않았습니다'}), 500 + except Exception as e: + logging.error(f"라벨 인쇄 오류: {e}") + return jsonify({'success': False, 'error': str(e)}), 500 + + def normalize_medication_name(med_name): """ 약품명 정제 - 밀리그램 등을 mg로 변환, 불필요한 부분 제거 diff --git a/backend/templates/pmr.html b/backend/templates/pmr.html index 8bd5b0a..20bbd46 100644 --- a/backend/templates/pmr.html +++ b/backend/templates/pmr.html @@ -2985,14 +2985,76 @@ document.getElementById('previewModal').style.display = 'none'; } - // 라벨 인쇄 (TODO: 구현) - function printLabels() { - const selected = Array.from(document.querySelectorAll('.med-check:checked')).map(c => c.dataset.code); - if (selected.length === 0) { + // 라벨 인쇄 (Brother QL 프린터) + async function printLabels() { + const checkboxes = document.querySelectorAll('.med-check:checked'); + if (checkboxes.length === 0) { alert('인쇄할 약품을 선택하세요'); return; } - alert(`선택된 약품 ${selected.length}개 인쇄 기능은 추후 구현 예정입니다.\n\n${selected.join('\n')}`); + + const patientName = document.querySelector('.patient-info h2')?.textContent?.trim() || ''; + let printedCount = 0; + let failedCount = 0; + + for (const checkbox of checkboxes) { + const tr = checkbox.closest('tr'); + if (!tr) continue; + + const cells = tr.querySelectorAll('td'); + const medName = tr.dataset.medName || cells[1]?.querySelector('.med-name')?.textContent?.replace(/^\d+/, '').trim() || ''; + const addInfo = tr.dataset.addInfo || ''; + const sungCode = tr.dataset.sungCode || ''; + const unit = tr.dataset.unit || '정'; + + // 용량 파싱 (1회 투약량) + const doseText = cells[2]?.textContent || '0'; + const dosage = parseFloat(doseText.replace(/[^0-9.]/g, '')) || 0; + + // 횟수 파싱 + const freqText = cells[3]?.textContent || '0'; + const frequency = parseInt(freqText.replace(/[^0-9]/g, '')) || 0; + + // 일수 파싱 + const durText = cells[4]?.textContent?.replace(/[^0-9]/g, '') || '0'; + const duration = parseInt(durText) || 0; + + try { + const res = await fetch('/pmr/api/label/print', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + patient_name: patientName, + med_name: medName, + add_info: addInfo, + dosage: dosage, + frequency: frequency, + duration: duration, + unit: unit, + sung_code: sungCode, + printer: '168' // 기본: QL-810W + }) + }); + const data = await res.json(); + + if (data.success) { + printedCount++; + console.log('Print success:', medName); + } else { + failedCount++; + console.error('Print failed:', medName, data.error); + } + } catch (err) { + failedCount++; + console.error('Print error:', medName, err); + } + } + + if (failedCount === 0) { + alert(`✅ ${printedCount}개 라벨 인쇄 완료!`); + } else { + alert(`⚠️ 인쇄 완료: ${printedCount}개\n실패: ${failedCount}개`); + } } // ═══════════════════════════════════════════════════════════════════════════ @@ -3424,8 +3486,8 @@