from pypos.core.base_view import BaseView from PySide6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QTableWidget, QTableWidgetItem, QLabel, QMessageBox, QDateEdit, QComboBox ) from PySide6.QtCore import QDate # upgraded: inherit base class class LoadTransaksiView(BaseView, QDialog): def __init__(self, controller): super().__init__() self.controller = controller self.setWindowTitle("Load Transaksi Tersimpan") from pypos.core.utils.dialog_size_helper import set_fixed_dialog_size set_fixed_dialog_size(self, 1200, 700) self.layout = QVBoxLayout() # PATCH[FrontEndAgent|HistoryFilter]: Strip filter dengan preset rentang dan opsi custom. filter_layout = QHBoxLayout() filter_label = QLabel("Mode Tampilan:") self.preset_combo = QComboBox() self.preset_combo.addItem("Hari Ini", "today") self.preset_combo.addItem("Kemarin", "yesterday") self.preset_combo.addItem("7 Hari Terakhir", "last7") self.preset_combo.addItem("30 Hari Terakhir", "last30") self.preset_combo.addItem("Rentang Bebas", "custom") self.preset_combo.currentIndexChanged.connect(self.on_preset_changed) reset_today_btn = QPushButton("Reset ke Hari Ini") reset_today_btn.clicked.connect(self.reset_to_today) tanggal_label = QLabel("Rentang Tanggal:") self.start_date_edit = QDateEdit() self.start_date_edit.setCalendarPopup(True) self.start_date_edit.setDate(QDate.currentDate()) self.end_date_edit = QDateEdit() self.end_date_edit.setCalendarPopup(True) self.end_date_edit.setDate(QDate.currentDate()) self.btn_filter = QPushButton("Tampilkan") self.btn_filter.clicked.connect(self.kirim_filter_tanggal) filter_layout.addWidget(filter_label) filter_layout.addWidget(self.preset_combo) filter_layout.addWidget(reset_today_btn) filter_layout.addSpacing(20) filter_layout.addWidget(tanggal_label) filter_layout.addWidget(self.start_date_edit) filter_layout.addWidget(QLabel("s.d")) filter_layout.addWidget(self.end_date_edit) filter_layout.addWidget(self.btn_filter) filter_layout.addStretch() self.layout.addLayout(filter_layout) self.table = QTableWidget(0, 5) self.table.setHorizontalHeaderLabels(["ID", "Nomer", "Tanggal","Customer", "Total Harga"]) # PATCH[FrontEndAgent|KeyboardNav]: Tangkap navigasi mouse dan keyboard self.table.cellClicked.connect(self.controller.transaksi_diklik) self.table.currentCellChanged.connect(self.on_current_cell_changed) from PySide6.QtWidgets import QHeaderView header = self.table.horizontalHeader() header.setSectionResizeMode(QHeaderView.Stretch) self.table.setStyleSheet("QTableWidget::item:selected { background: #d0d0d0; color: black; }") self.layout.addWidget(self.table) # PATCH[FrontEndAgent|ToggleDetail]: Header preview dengan button toggle preview_header_layout = QHBoxLayout() self.preview_label = QLabel("📝 Preview Detail Transaksi:") self.btn_toggle_detail = QPushButton("📦 Detail Barang") self.btn_toggle_detail.setStyleSheet(""" QPushButton { background-color: #17a2b8; color: white; font-weight: bold; padding: 4px 12px; border-radius: 5px; font-size: 8pt; } QPushButton:hover { background-color: #138496; } """) self.btn_toggle_detail.clicked.connect(self.toggle_detail_view) self.btn_toggle_detail.setVisible(False) # Hidden until data loaded preview_header_layout.addWidget(self.preview_label) preview_header_layout.addWidget(self.btn_toggle_detail) preview_header_layout.addStretch() self.layout.addLayout(preview_header_layout) # PATCH[FrontEndAgent|DetailInfo]: Section info transaksi lengkap (compact) self.info_transaksi_label = QLabel("") # Store data for toggle self.current_header_data = None self.current_detail_rows = None self.is_showing_items = False self.info_transaksi_label.setStyleSheet(""" QLabel { background-color: #f8f9fa; border: 2px solid #28a745; border-radius: 6px; padding: 12px; font-size: 10pt; line-height: 1.4; min-height: 180px; } """) self.info_transaksi_label.setWordWrap(True) self.layout.addWidget(self.info_transaksi_label) self.button_layout = QHBoxLayout() self.btn_close = QPushButton("🚪 Close (ESC)") self.btn_close.setStyleSheet(""" QPushButton { background-color: #6c757d; color: white; font-weight: bold; padding: 10px 30px; border-radius: 8px; font-size: 10pt; } QPushButton:hover { background-color: #5a6268; } """) self.btn_close.clicked.connect(self.reject) self.btn_close.setShortcut("Esc") self.button_layout.addStretch() self.button_layout.addWidget(self.btn_close) self.layout.addLayout(self.button_layout) self.setLayout(self.layout) # PATCH[FrontEndAgent|InitState]: Inisialisasi diserahkan ke controller setelah view siap. def render_transaksi_list(self, rows): """Render daftar transaksi tersimpan/history ke tabel utama.""" self.table.setRowCount(0) for row_data in rows: row = self.table.rowCount() self.table.insertRow(row) for col, val in enumerate(row_data[:5]): self.table.setItem(row, col, QTableWidgetItem(str(val))) def render_detail_transaksi(self, detail_rows): """Render detail transaksi yang dipilih.""" self.detail_table.setRowCount(0) for item in detail_rows: row = self.detail_table.rowCount() self.detail_table.insertRow(row) for col, val in enumerate(item): self.detail_table.setItem(row, col, QTableWidgetItem(str(val))) def warn_pilih_transaksi(self): QMessageBox.warning(self, "Pilih Transaksi", "Pilih transaksi untuk diload.") def kirim_filter_tanggal(self): start = self.start_date_edit.date() end = self.end_date_edit.date() if start > end: QMessageBox.warning(self, "Validasi Tanggal", "Tanggal mulai tidak boleh melebihi tanggal akhir.") return start_str = start.toString("yyyy-MM-dd") end_str = end.toString("yyyy-MM-dd") # Pastikan mode ditandai sebagai custom tanpa memicu handler preset. custom_idx = self.preset_combo.findData("custom") if custom_idx != -1 and self.preset_combo.currentIndex() != custom_idx: self.preset_combo.blockSignals(True) self.preset_combo.setCurrentIndex(custom_idx) self.preset_combo.blockSignals(False) self.controller.filter_by_date_range(start_str, end_str) def tampilkan_history_rows(self, rows): """Render daftar transaksi ke tabel utama.""" self.table.setRowCount(0) for row_data in rows: row = self.table.rowCount() self.table.insertRow(row) for col, val in enumerate(row_data[:5]): self.table.setItem(row, col, QTableWidgetItem(str(val))) def on_preset_changed(self, index): mode = self.preset_combo.itemData(index) # Aktifkan rentang tanggal selalu; preset mengisi otomatis, custom memakai input. self.start_date_edit.setEnabled(True) self.end_date_edit.setEnabled(True) self.btn_filter.setEnabled(True) if mode != "custom": # Hindari pemanggilan sebelum controller siap (saat konstruksi). if hasattr(self.controller, "filter_by_preset"): self.controller.filter_by_preset(mode) def reset_to_today(self): idx = self.preset_combo.findData("today") if idx != -1: self.preset_combo.setCurrentIndex(idx) def set_date_inputs(self, start_iso, end_iso): """ Menerapkan tanggal ke input untuk menjaga sinkronisasi UI. """ try: start_qdate = QDate.fromString(start_iso, "yyyy-MM-dd") end_qdate = QDate.fromString(end_iso, "yyyy-MM-dd") if start_qdate.isValid(): self.start_date_edit.setDate(start_qdate) if end_qdate.isValid(): self.end_date_edit.setDate(end_qdate) except Exception: # Fail silently jika format tidak valid; UI akan tetap pada nilai sebelumnya. pass def tampilkan_detail_transaksi(self, header_data, detail_rows): """PATCH[FrontEndAgent|DetailInfo]: Tampilkan info transaksi lengkap dengan format informatif""" if not header_data: self.info_transaksi_label.setText("❌ Data transaksi tidak ditemukan") self.btn_toggle_detail.setVisible(False) return # Store data for toggle self.current_header_data = header_data self.current_detail_rows = detail_rows self.is_showing_items = False self.btn_toggle_detail.setVisible(True) self.btn_toggle_detail.setText("📦 Detail Barang") self.preview_label.setText("📝 Preview Detail Transaksi:") # Parse header data ( customer_id, customer_nama, diskon_persen, ppn_persen, transaksi_nilai, dtime, kasir_id, kasir_nama, metode_pembayaran, bank_id, bank_nama, settlement_id, jumlah_bayar, kembalian, approval_code, no_kartu ) = header_data # Format rupiah def fmt_rp(nilai): if nilai is None: return "Rp 0,00" return f"Rp {float(nilai):,.2f}".replace(",", "X").replace(".", ",").replace("X", ".") # PATCH[FrontEndAgent|PPNFix]: ppn_persen di DB sebenarnya nilai Rupiah, bukan persen # Karena data lama tidak konsisten, tampilkan nilai Rupiah saja ppn_display = "" diskon_display = "" if ppn_persen and ppn_persen > 0: ppn_display = f"{fmt_rp(ppn_persen)}" else: ppn_display = "Rp 0,00" if diskon_persen and diskon_persen > 0: diskon_display = f"{diskon_persen:.0f}%" else: diskon_display = "0%" # Build items list HTML items_html = "" for idx, item in enumerate(detail_rows, 1): try: harga = float(item[2]) jumlah = float(item[3]) diskon_item = float(item[4]) if item[4] else 0 subtotal = (harga * jumlah) - diskon_item satuan = item[5] if len(item) > 5 else "" items_html += f""" {idx} {item[0]} {item[1]} {fmt_rp(harga)} {jumlah} {satuan} {fmt_rp(diskon_item) if diskon_item > 0 else '-'} {fmt_rp(subtotal)} """ except: pass # Format waktu from datetime import datetime try: dt = datetime.fromisoformat(dtime) waktu_str = dt.strftime("%d/%m/%Y %H:%M:%S") tanggal_str = dt.strftime("%d %B %Y") jam_str = dt.strftime("%H:%M:%S") except: waktu_str = dtime tanggal_str = dtime jam_str = "-" # Status settlement if settlement_id != 1: settlement_status = "✅ Settled" settlement_color = "#28a745" settlement_tooltip = "Transaksi sudah disetor/dilaporkan" else: settlement_status = "⏳ Pending" settlement_color = "#ffc107" settlement_tooltip = "Belum disetor (Menunggu Settlement)" # Metode pembayaran icon metode_icon = { 'tunai': '💵', 'kredit': '💳', 'debit': '🏧' }.get(metode_pembayaran or 'tunai', '💰') # Info kartu (4 digit terakhir untuk keamanan) - inline kartu_info = "" if no_kartu and no_kartu.strip(): kartu_masked = "****" + no_kartu[-4:] if len(no_kartu) > 4 else no_kartu kartu_info = f" | {kartu_masked}" # Build info HTML (larger, more readable layout) info_html = f"""
👤 PEMBELI
{customer_nama or 'tunai'}
👨‍💼 KASIR
{kasir_nama or 'System'}
🕐 WAKTU
{tanggal_str} {jam_str}
{metode_icon} BAYAR
{(metode_pembayaran or 'tunai').upper()} - {bank_nama or 'Tunai'}{kartu_info}
SETTLEMENT
{settlement_status}
Subtotal: {fmt_rp((transaksi_nilai or 0) - (ppn_persen or 0))}
Diskon ({diskon_display}): - Rp 0,00
PPN: + {ppn_display}
TOTAL: {fmt_rp(transaksi_nilai)}
Dibayar: {fmt_rp(jumlah_bayar)}
Kembalian: {fmt_rp(kembalian)}
📦 {len(detail_rows)} item dalam transaksi ini. Klik button "Detail Barang" untuk melihat daftar lengkap.
""" self.info_transaksi_label.setText(info_html) def on_current_cell_changed(self, currentRow, currentColumn, previousRow, previousColumn): """PATCH[FrontEndAgent|KeyboardNav]: Handler untuk navigasi keyboard (arrow keys)""" if currentRow >= 0: # Panggil controller dengan row dan column (sama seperti cellClicked) self.controller.transaksi_diklik(currentRow, currentColumn) def toggle_detail_view(self): """PATCH[FrontEndAgent|ToggleDetail]: Toggle antara info transaksi dan detail barang""" if not self.current_header_data or not self.current_detail_rows: return self.is_showing_items = not self.is_showing_items if self.is_showing_items: # Tampilkan detail barang saja self.btn_toggle_detail.setText("📋 Info Transaksi") self.preview_label.setText("📦 Detail Barang:") self.tampilkan_detail_barang_saja(self.current_detail_rows) else: # Tampilkan info transaksi lengkap self.btn_toggle_detail.setText("📦 Detail Barang") self.preview_label.setText("📝 Preview Detail Transaksi:") self.tampilkan_detail_transaksi(self.current_header_data, self.current_detail_rows) def tampilkan_detail_barang_saja(self, detail_rows): """PATCH[FrontEndAgent|ToggleDetail]: Tampilkan hanya tabel detail barang (full height)""" if not detail_rows: self.info_transaksi_label.setText("❌ Tidak ada detail barang") return # Format rupiah def fmt_rp(nilai): if nilai is None: return "Rp 0,00" return f"Rp {float(nilai):,.2f}".replace(",", "X").replace(".", ",").replace("X", ".") # Build items HTML items_html = "" total_qty = 0 total_nilai = 0 for idx, item in enumerate(detail_rows, 1): try: harga = float(item[2]) jumlah = float(item[3]) diskon_item = float(item[4]) if item[4] else 0 subtotal = (harga * jumlah) - diskon_item satuan = item[5] if len(item) > 5 else "" total_qty += jumlah total_nilai += subtotal items_html += f""" {idx} {item[0]} {item[1]} {fmt_rp(harga)} {jumlah} {satuan} {fmt_rp(diskon_item) if diskon_item > 0 else '-'} {fmt_rp(subtotal)} """ except: pass # Build HTML dengan tabel full size detail_html = f"""
📦 DAFTAR BARANG ({len(detail_rows)} item) Total Qty: {total_qty} | Total Nilai: {fmt_rp(total_nilai)}
{items_html}
No Kode Nama Barang Harga Qty Diskon Subtotal
""" self.info_transaksi_label.setText(detail_html)