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 라벨 생성""" # 선택된 행 확인