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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user