feat: GUI 칼럼 설정 저장, 010 전화번호 UX 개선, 품목 상세 조회

- GUI: SALES_COLUMNS 상수 정의, 칼럼 폭/윈도우 위치 gui_settings.json에 저장
- 전화번호 입력: 적립페이지/마이페이지에서 010 고정 + 뒷번호만 입력
- 적립페이지: MSSQL SALE_SUB에서 구매 품목 조회 및 토글 표시
- 마이페이지: 적립 내역 탭 시 품목 상세 AJAX 조회 (캐시 적용)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
thug0bin
2026-02-25 01:17:45 +09:00
parent 774c199c1a
commit 82220a4a44
6 changed files with 390 additions and 81 deletions

View File

@@ -13,7 +13,7 @@ from PyQt5.QtWidgets import (
QDialog, QMessageBox, QDateEdit, QCheckBox
)
from PyQt5.QtCore import QThread, pyqtSignal, Qt, QDate, QTimer
from PyQt5.QtGui import QFont
from PyQt5.QtGui import QFont, QColor
# 데이터베이스 연결 (backend/ 폴더를 Python 경로에 추가)
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
@@ -556,6 +556,20 @@ class POSSalesGUI(QMainWindow):
"""
POS 판매 내역 조회 메인 GUI
"""
CONFIG_FILE = os.path.join(os.path.dirname(__file__), 'gui_settings.json')
# 판매 테이블 컬럼 정의: (헤더명, 기본폭, 데이터키)
SALES_COLUMNS = [
('주문번호', 150, 'order_no'),
('시간', 70, 'time'),
('금액', 100, 'amount'),
('고객명', 80, 'customer'),
('품목수', 55, 'item_count'),
('적립자', 90, 'claimed_name'),
('전화번호', 120, 'claimed_phone'),
('적립포인트', 90, 'claimed_points'),
('QR', 50, 'qr_issued'),
]
def __init__(self):
super().__init__()
@@ -563,12 +577,17 @@ class POSSalesGUI(QMainWindow):
self.sales_thread = None
self.qr_thread = None # QR 생성 스레드 추가
self.sales_data = []
self._gui_settings = self._load_settings()
self.init_ui()
def init_ui(self):
"""UI 초기화"""
self.setWindowTitle('POS 판매 조회')
self.setGeometry(100, 100, 1300, 600)
saved_geo = self._gui_settings.get('window_geometry')
if saved_geo and len(saved_geo) == 4:
self.setGeometry(*saved_geo)
else:
self.setGeometry(100, 100, 1300, 600)
# 중앙 위젯
central_widget = QWidget()
@@ -659,19 +678,18 @@ class POSSalesGUI(QMainWindow):
sales_group.setLayout(sales_layout)
self.sales_table = QTableWidget()
self.sales_table.setColumnCount(9)
self.sales_table.setHorizontalHeaderLabels([
'주문번호', '시간', '금액', '고객명', '품목수', '적립자명', '전화번호', '적립포인트', 'QR'
])
self.sales_table.setColumnWidth(0, 160)
self.sales_table.setColumnWidth(1, 70)
self.sales_table.setColumnWidth(2, 110)
self.sales_table.setColumnWidth(3, 100)
self.sales_table.setColumnWidth(4, 70)
self.sales_table.setColumnWidth(5, 100)
self.sales_table.setColumnWidth(6, 120)
self.sales_table.setColumnWidth(7, 100)
self.sales_table.setColumnWidth(8, 60)
col_count = len(self.SALES_COLUMNS)
self.sales_table.setColumnCount(col_count)
self.sales_table.setHorizontalHeaderLabels([c[0] for c in self.SALES_COLUMNS])
# 컬럼 폭: 저장된 값 우선, 없으면 SALES_COLUMNS 기본값
saved_widths = self._gui_settings.get('sales_column_widths')
for i, (_, default_w, _) in enumerate(self.SALES_COLUMNS):
w = saved_widths[i] if saved_widths and len(saved_widths) == col_count else default_w
self.sales_table.setColumnWidth(i, w)
self.sales_table.horizontalHeader().setStretchLastSection(True)
self.sales_table.horizontalHeader().sectionResized.connect(self._on_column_resized)
self.sales_table.setSelectionBehavior(QTableWidget.SelectRows)
self.sales_table.doubleClicked.connect(self.show_sale_detail)
self.sales_table.cellClicked.connect(self.on_cell_clicked)
@@ -745,79 +763,85 @@ class POSSalesGUI(QMainWindow):
def populate_table(self, sales_list):
"""QTableWidget에 데이터 채우기"""
# 컬럼 인덱스 맵 (SALES_COLUMNS 순서 기반)
COL = {key: i for i, (_, _, key) in enumerate(self.SALES_COLUMNS)}
self.sales_table.setRowCount(len(sales_list))
# 적립 완료 셀 스타일
CLAIMED_COLOR = QColor('#4CAF50')
def make_claimed_font(underline=True):
f = QFont()
f.setBold(True)
if underline:
f.setUnderline(True)
return f
for row, sale in enumerate(sales_list):
# 주문번호
self.sales_table.setItem(row, 0, QTableWidgetItem(sale['order_no']))
self.sales_table.setItem(row, COL['order_no'],
QTableWidgetItem(sale['order_no']))
# 시간
self.sales_table.setItem(row, 1, QTableWidgetItem(sale['time']))
self.sales_table.setItem(row, COL['time'],
QTableWidgetItem(sale['time']))
# 금액 (우측 정렬, 천단위 콤마)
amount_item = QTableWidgetItem(f"{sale['amount']:,.0f}")
amount_item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
self.sales_table.setItem(row, 2, amount_item)
self.sales_table.setItem(row, COL['amount'], amount_item)
# 고객명 (MSSQL)
self.sales_table.setItem(row, 3, QTableWidgetItem(sale['customer']))
# 고객명 (MSSQL POS)
self.sales_table.setItem(row, COL['customer'],
QTableWidgetItem(sale['customer']))
# 품목수 (중앙 정렬)
count_item = QTableWidgetItem(str(sale['item_count']))
count_item.setTextAlignment(Qt.AlignCenter)
self.sales_table.setItem(row, 4, count_item)
self.sales_table.setItem(row, COL['item_count'], count_item)
# 적립자 (SQLite)
from PyQt5.QtGui import QColor, QFont
# 적립자 (SQLite 마일리지)
claimed_name_item = QTableWidgetItem(sale['claimed_name'])
if sale['claimed_name']:
claimed_name_item.setForeground(QColor('#4CAF50'))
font = QFont()
font.setBold(True)
font.setUnderline(True) # 밑줄 추가로 클릭 가능 표시
claimed_name_item.setFont(font)
claimed_name_item.setForeground(CLAIMED_COLOR)
claimed_name_item.setFont(make_claimed_font())
claimed_name_item.setToolTip('클릭하여 회원 마일리지 내역 보기')
self.sales_table.setItem(row, 5, claimed_name_item)
self.sales_table.setItem(row, COL['claimed_name'], claimed_name_item)
# 전화번호 (SQLite)
# 전화번호 (SQLite 마일리지)
claimed_phone_item = QTableWidgetItem(sale['claimed_phone'])
if sale['claimed_phone']:
claimed_phone_item.setForeground(QColor('#4CAF50'))
font = QFont()
font.setBold(True)
font.setUnderline(True) # 밑줄 추가로 클릭 가능 표시
claimed_phone_item.setFont(font)
claimed_phone_item.setForeground(CLAIMED_COLOR)
claimed_phone_item.setFont(make_claimed_font())
claimed_phone_item.setToolTip('클릭하여 회원 마일리지 내역 보기')
self.sales_table.setItem(row, 6, claimed_phone_item)
self.sales_table.setItem(row, COL['claimed_phone'], claimed_phone_item)
# 적립포인트 (SQLite)
claimed_points_item = QTableWidgetItem(f"{sale['claimed_points']:,}P" if sale['claimed_points'] > 0 else "")
# 적립포인트 (SQLite 마일리지)
points_text = f"{sale['claimed_points']:,}P" if sale['claimed_points'] > 0 else ""
claimed_points_item = QTableWidgetItem(points_text)
if sale['claimed_points'] > 0:
claimed_points_item.setForeground(QColor('#4CAF50'))
claimed_points_item.setForeground(CLAIMED_COLOR)
claimed_points_item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
font = QFont()
font.setBold(True)
font.setUnderline(True) # 밑줄 추가로 클릭 가능 표시
claimed_points_item.setFont(font)
claimed_points_item.setFont(make_claimed_font())
claimed_points_item.setToolTip('클릭하여 회원 마일리지 내역 보기')
self.sales_table.setItem(row, 7, claimed_points_item)
self.sales_table.setItem(row, COL['claimed_points'], claimed_points_item)
# QR 발행 여부 (SQLite)
# QR 발행 여부
qr_status_item = QTableWidgetItem()
qr_status_item.setTextAlignment(Qt.AlignCenter)
if sale['qr_issued']:
qr_status_item.setText('')
qr_status_item.setForeground(QColor('#4CAF50'))
font = QFont()
font.setBold(True)
font.setPointSize(14)
qr_status_item.setFont(font)
qr_status_item.setForeground(CLAIMED_COLOR)
f = QFont()
f.setBold(True)
f.setPointSize(14)
qr_status_item.setFont(f)
qr_status_item.setToolTip('QR 발행 완료')
else:
qr_status_item.setText('-')
qr_status_item.setForeground(QColor('#BDBDBD'))
qr_status_item.setToolTip('QR 미발행')
self.sales_table.setItem(row, 8, qr_status_item)
self.sales_table.setItem(row, COL['qr_issued'], qr_status_item)
def on_query_error(self, error_msg):
"""DB 조회 에러 처리"""
@@ -1003,8 +1027,36 @@ class POSSalesGUI(QMainWindow):
self.status_label.setStyleSheet('color: red; font-size: 12px; padding: 5px;')
QMessageBox.critical(self, '오류', f'QR 생성 실패:\n{message}')
# --- 설정 저장/로드 ---
def _load_settings(self):
try:
with open(self.CONFIG_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return {}
def _save_settings(self):
try:
with open(self.CONFIG_FILE, 'w', encoding='utf-8') as f:
json.dump(self._gui_settings, f, ensure_ascii=False, indent=2)
except Exception:
pass
def _on_column_resized(self, index, old_size, new_size):
widths = [self.sales_table.columnWidth(i) for i in range(self.sales_table.columnCount())]
self._gui_settings['sales_column_widths'] = widths
def closeEvent(self, event):
"""종료 시 정리"""
"""종료 시 정리 + 설정 저장"""
# 컬럼 폭 최종 저장
if hasattr(self, 'sales_table'):
widths = [self.sales_table.columnWidth(i) for i in range(self.sales_table.columnCount())]
self._gui_settings['sales_column_widths'] = widths
# 윈도우 위치/크기 저장
geo = self.geometry()
self._gui_settings['window_geometry'] = [geo.x(), geo.y(), geo.width(), geo.height()]
self._save_settings()
# 자동 새로고침 타이머 중지
if hasattr(self, 'refresh_timer'):
self.refresh_timer.stop()