pharmacy-pos-qr-system/backend/gui/pos_thermal.py
시골약사 e499e19342 feat: 더미 POS GUI 및 영수증 프린터 설정 추가
- 바코드 스캔 → 제품 조회 → 장바구니 → 결제 흐름의 더미 POS GUI 추가
- ESC/POS 영수증 프린터 설정 다이얼로그 추가
- barcode_reader_gui.py dbsetup import 경로 수정
- POS 프린터 config.json 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 15:02:48 +09:00

223 lines
7.3 KiB
Python

# pos_settings_dialog.py
# POS 영수증 프린터 설정 다이얼로그
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
QLineEdit, QFormLayout, QMessageBox
)
from PyQt5.QtCore import Qt
import json
import os
import socket
import time
class POSSettingsDialog(QDialog):
"""POS 영수증 프린터 설정"""
def __init__(self, parent=None):
super().__init__(parent)
self.config_path = os.path.join(os.path.dirname(__file__), '..', 'config.json')
self.setWindowTitle("POS 영수증 프린터 설정")
self.setMinimumSize(500, 300)
self.init_ui()
self.load_settings()
def init_ui(self):
layout = QVBoxLayout()
# 제목
title = QLabel("POS 영수증 프린터 설정")
title.setStyleSheet("font-size: 16px; font-weight: bold; margin-bottom: 10px;")
layout.addWidget(title)
# 설명
desc = QLabel("ESC/POS 프로토콜을 지원하는 영수증 프린터 설정\n올댓포스 AGENT가 설치된 PC IP를 입력하세요")
desc.setStyleSheet("color: gray; margin-bottom: 20px;")
layout.addWidget(desc)
# 폼 레이아웃
form_layout = QFormLayout()
# IP 주소
self.ip_input = QLineEdit()
self.ip_input.setPlaceholderText("예: 192.168.0.174")
form_layout.addRow("IP 주소 *", self.ip_input)
# 포트
self.port_input = QLineEdit()
self.port_input.setText("9100")
form_layout.addRow("포트", self.port_input)
# 프린터 이름
self.name_input = QLineEdit()
self.name_input.setPlaceholderText("예: 메인 POS 프린터")
form_layout.addRow("프린터 이름", self.name_input)
layout.addLayout(form_layout)
layout.addStretch()
# 버튼들
button_layout = QHBoxLayout()
self.test_button = QPushButton("테스트 인쇄")
self.test_button.clicked.connect(self.test_print)
self.test_button.setStyleSheet("""
QPushButton {
background-color: #2196F3;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
font-weight: bold;
}
QPushButton:hover {
background-color: #1976D2;
}
""")
button_layout.addWidget(self.test_button)
button_layout.addStretch()
self.cancel_button = QPushButton("취소")
self.cancel_button.clicked.connect(self.reject)
button_layout.addWidget(self.cancel_button)
self.save_button = QPushButton("저장")
self.save_button.clicked.connect(self.save_settings)
self.save_button.setStyleSheet("""
QPushButton {
background-color: #4CAF50;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
font-weight: bold;
}
QPushButton:hover {
background-color: #45a049;
}
""")
button_layout.addWidget(self.save_button)
layout.addLayout(button_layout)
self.setLayout(layout)
def load_settings(self):
"""설정 불러오기"""
try:
if os.path.exists(self.config_path):
with open(self.config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
pos_config = config.get('pos_printer', {})
self.ip_input.setText(pos_config.get('ip', ''))
self.port_input.setText(str(pos_config.get('port', 9100)))
self.name_input.setText(pos_config.get('name', ''))
except Exception as e:
print(f"[POS Settings] 설정 로드 오류: {e}")
def save_settings(self):
"""설정 저장"""
ip = self.ip_input.text().strip()
port = self.port_input.text().strip()
name = self.name_input.text().strip()
# 유효성 검사
if not ip:
QMessageBox.warning(self, "입력 오류", "IP 주소를 입력해주세요.")
return
try:
port_num = int(port)
except ValueError:
QMessageBox.warning(self, "입력 오류", "포트는 숫자여야 합니다.")
return
# 설정 저장
try:
config = {}
if os.path.exists(self.config_path):
with open(self.config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
config['pos_printer'] = {
'ip': ip,
'port': port_num,
'name': name if name else f"POS Printer ({ip})"
}
with open(self.config_path, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=4, ensure_ascii=False)
QMessageBox.information(self, "성공", "POS 프린터 설정이 저장되었습니다.")
self.accept()
except Exception as e:
QMessageBox.warning(self, "오류", f"설정 저장 실패: {str(e)}")
def test_print(self):
"""테스트 인쇄"""
ip = self.ip_input.text().strip()
port = self.port_input.text().strip()
if not ip:
QMessageBox.warning(self, "입력 오류", "IP 주소를 입력해주세요.")
return
try:
port_num = int(port)
except ValueError:
QMessageBox.warning(self, "입력 오류", "포트는 숫자여야 합니다.")
return
# ESC/POS 테스트 인쇄
try:
# ESC/POS 명령어
ESC = b'\x1b'
INIT = ESC + b'@' # 프린터 초기화
CUT = ESC + b'd\x03' # 용지 커트
# 테스트 메시지
message = f"""
================================
POS 프린터 테스트!
================================
IP: {ip}
Port: {port_num}
Time: {time.strftime('%Y-%m-%d %H:%M:%S')}
ESC/POS 명령으로 인쇄됨
정상 작동 확인!
================================
"""
# EUC-KR 인코딩 (한글 지원)
message_bytes = message.encode('euc-kr')
command = INIT + message_bytes + b'\n\n\n' + CUT
# TCP 소켓으로 전송
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
sock.connect((ip, port_num))
sock.sendall(command)
sock.close()
QMessageBox.information(
self, "성공",
f"테스트 인쇄 명령을 전송했습니다!\n\n"
f"IP: {ip}:{port_num}\n\n"
f"POS 프린터에서 영수증 출력을 확인하세요."
)
except socket.timeout:
QMessageBox.warning(self, "실패", f"연결 시간 초과\n\n프린터가 켜져있는지 확인하세요.")
except ConnectionRefusedError:
QMessageBox.warning(self, "실패", f"연결 거부됨\n\nIP 주소와 포트를 확인하세요.")
except UnicodeEncodeError:
QMessageBox.warning(self, "인코딩 오류", "EUC-KR로 인코딩할 수 없는 문자가 있습니다.")
except Exception as e:
QMessageBox.warning(self, "실패", f"테스트 인쇄 실패\n\n{type(e).__name__}: {str(e)}")