diff --git a/backend/gui/pos_sales_gui.py b/backend/gui/pos_sales_gui.py
index 90137f3..034a0a2 100644
--- a/backend/gui/pos_sales_gui.py
+++ b/backend/gui/pos_sales_gui.py
@@ -363,6 +363,151 @@ class SaleDetailDialog(QDialog):
conn.close()
+class UserMileageDialog(QDialog):
+ """
+ 회원 마일리지 내역 조회 팝업
+ 적립 사용자 클릭 시 해당 회원의 적립 내역 표시
+ """
+
+ def __init__(self, phone, parent=None):
+ """
+ Args:
+ phone: 전화번호
+ parent: 부모 위젯
+ """
+ super().__init__(parent)
+ self.phone = phone
+ self.setWindowTitle(f'회원 마일리지 내역')
+ self.setModal(True)
+ self.resize(800, 500)
+ self.init_ui()
+ self.load_user_info()
+
+ def init_ui(self):
+ """UI 초기화"""
+ layout = QVBoxLayout()
+
+ # 회원 정보 그룹박스
+ info_group = QGroupBox('회원 정보')
+ info_layout = QVBoxLayout()
+ info_group.setLayout(info_layout)
+
+ self.info_label = QLabel('조회 중...')
+ self.info_label.setStyleSheet('font-size: 13px; padding: 10px; background: #f5f7fa; border-radius: 8px;')
+ info_layout.addWidget(self.info_label)
+
+ layout.addWidget(info_group)
+
+ # 적립 내역 테이블
+ history_group = QGroupBox('적립 내역')
+ history_layout = QVBoxLayout()
+ history_group.setLayout(history_layout)
+
+ self.history_table = QTableWidget()
+ self.history_table.setColumnCount(5)
+ self.history_table.setHorizontalHeaderLabels([
+ '날짜', '구분', '포인트', '잔액', '설명'
+ ])
+ self.history_table.setColumnWidth(0, 150)
+ self.history_table.setColumnWidth(1, 80)
+ self.history_table.setColumnWidth(2, 100)
+ self.history_table.setColumnWidth(3, 100)
+ self.history_table.setColumnWidth(4, 300)
+ history_layout.addWidget(self.history_table)
+
+ layout.addWidget(history_group)
+
+ # 닫기 버튼
+ close_btn = QPushButton('닫기')
+ close_btn.setStyleSheet('background-color: #2196F3; color: white; padding: 8px; font-weight: bold;')
+ close_btn.clicked.connect(self.close)
+ layout.addWidget(close_btn)
+
+ self.setLayout(layout)
+
+ def load_user_info(self):
+ """SQLite에서 회원 정보 및 적립 내역 조회"""
+ conn = None
+ try:
+ db_manager = DatabaseManager()
+ conn = db_manager.get_sqlite_connection()
+ cursor = conn.cursor()
+
+ # 전화번호로 사용자 조회
+ cursor.execute("""
+ SELECT id, nickname, phone, mileage_balance, created_at
+ FROM users WHERE phone = ?
+ """, (self.phone,))
+
+ user = cursor.fetchone()
+
+ if not user:
+ self.info_label.setText('등록되지 않은 회원입니다.')
+ return
+
+ # 회원 정보 표시
+ info_text = f"""
+ 이름: {user['nickname']}
+ 전화번호: {user['phone']}
+ 포인트 잔액: {user['mileage_balance']:,}P
+ 가입일: {user['created_at']}
+ """
+ self.info_label.setText(info_text)
+
+ # 적립 내역 조회
+ cursor.execute("""
+ SELECT points, balance_after, reason, description, created_at
+ FROM mileage_ledger
+ WHERE user_id = ?
+ ORDER BY created_at DESC
+ LIMIT 50
+ """, (user['id'],))
+
+ transactions = cursor.fetchall()
+
+ # 테이블에 데이터 채우기
+ self.history_table.setRowCount(len(transactions))
+ for row_idx, tx in enumerate(transactions):
+ from PyQt5.QtGui import QColor
+
+ # 날짜
+ date_item = QTableWidgetItem(tx['created_at'])
+ self.history_table.setItem(row_idx, 0, date_item)
+
+ # 구분 (CLAIM, USE 등)
+ reason_text = '적립' if tx['reason'] == 'CLAIM' else '사용'
+ reason_item = QTableWidgetItem(reason_text)
+ reason_item.setTextAlignment(Qt.AlignCenter)
+ if tx['reason'] == 'CLAIM':
+ reason_item.setForeground(QColor('#4CAF50'))
+ else:
+ reason_item.setForeground(QColor('#f03e3e'))
+ self.history_table.setItem(row_idx, 1, reason_item)
+
+ # 포인트 (우측 정렬)
+ points_item = QTableWidgetItem(f"{tx['points']:+,}P")
+ points_item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
+ if tx['points'] > 0:
+ points_item.setForeground(QColor('#4CAF50'))
+ else:
+ points_item.setForeground(QColor('#f03e3e'))
+ self.history_table.setItem(row_idx, 2, points_item)
+
+ # 잔액 (우측 정렬)
+ balance_item = QTableWidgetItem(f"{tx['balance_after']:,}P")
+ balance_item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
+ self.history_table.setItem(row_idx, 3, balance_item)
+
+ # 설명
+ self.history_table.setItem(row_idx, 4, QTableWidgetItem(tx['description']))
+
+ except Exception as e:
+ QMessageBox.critical(self, '오류', f'회원 정보 조회 실패:\n{str(e)}')
+ finally:
+ if conn:
+ conn.close()
+
+
class POSSalesGUI(QMainWindow):
"""
POS 판매 내역 조회 메인 GUI
@@ -447,6 +592,7 @@ class POSSalesGUI(QMainWindow):
self.sales_table.setColumnWidth(7, 100)
self.sales_table.setSelectionBehavior(QTableWidget.SelectRows)
self.sales_table.doubleClicked.connect(self.show_sale_detail)
+ self.sales_table.cellClicked.connect(self.on_cell_clicked)
sales_layout.addWidget(self.sales_table)
@@ -546,7 +692,9 @@ class POSSalesGUI(QMainWindow):
claimed_name_item.setForeground(QColor('#4CAF50'))
font = QFont()
font.setBold(True)
+ font.setUnderline(True) # 밑줄 추가로 클릭 가능 표시
claimed_name_item.setFont(font)
+ claimed_name_item.setToolTip('클릭하여 회원 마일리지 내역 보기')
self.sales_table.setItem(row, 5, claimed_name_item)
# 전화번호 (SQLite)
@@ -555,7 +703,9 @@ class POSSalesGUI(QMainWindow):
claimed_phone_item.setForeground(QColor('#4CAF50'))
font = QFont()
font.setBold(True)
+ font.setUnderline(True) # 밑줄 추가로 클릭 가능 표시
claimed_phone_item.setFont(font)
+ claimed_phone_item.setToolTip('클릭하여 회원 마일리지 내역 보기')
self.sales_table.setItem(row, 6, claimed_phone_item)
# 적립포인트 (SQLite)
@@ -565,7 +715,9 @@ class POSSalesGUI(QMainWindow):
claimed_points_item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
font = QFont()
font.setBold(True)
+ font.setUnderline(True) # 밑줄 추가로 클릭 가능 표시
claimed_points_item.setFont(font)
+ claimed_points_item.setToolTip('클릭하여 회원 마일리지 내역 보기')
self.sales_table.setItem(row, 7, claimed_points_item)
def on_query_error(self, error_msg):
@@ -585,6 +737,24 @@ class POSSalesGUI(QMainWindow):
detail_dialog = SaleDetailDialog(order_no, self)
detail_dialog.exec_()
+ def on_cell_clicked(self, row, column):
+ """테이블 셀 클릭 이벤트 - 적립 사용자 클릭 시 마일리지 내역 표시"""
+ # 컬럼 5(적립자명), 6(전화번호), 7(적립포인트) 중 하나를 클릭했는지 확인
+ if column not in [5, 6, 7]:
+ return
+
+ # 전화번호 가져오기 (6번 컬럼)
+ phone_item = self.sales_table.item(row, 6)
+ if not phone_item or not phone_item.text():
+ # 적립 사용자가 없는 경우
+ return
+
+ phone = phone_item.text()
+
+ # 회원 마일리지 내역 Dialog 표시
+ mileage_dialog = UserMileageDialog(phone, self)
+ mileage_dialog.exec_()
+
def generate_qr_label(self):
"""선택된 판매 건에 대해 QR 라벨 생성"""
# 선택된 행 확인