diff --git a/main.py b/main.py index 9db2bb5..04826d6 100644 --- a/main.py +++ b/main.py @@ -1135,7 +1135,16 @@ class App: customer_entry.grid(row=3, column=1, pady=5) ttk.Label(frame, text="(Khusus untuk Pembeli)", font=("Arial", 7), foreground='gray').grid(row=4, column=1, sticky='w') - ttk.Button(frame, text="Login", command=self.handle_login).grid(row=5, column=0, columnspan=2, pady=12) + # TAMBAHAN BARU: Input nomor meja (untuk role pembeli) + ttk.Label(frame, text="Nomor Meja:", font=("Arial", 9)).grid(row=5, column=0, sticky='e', pady=5) + self.customer_meja_var = tk.StringVar() + meja_entry = ttk.Entry(frame, textvariable=self.customer_meja_var, width=30) + meja_entry.grid(row=5, column=1, pady=5) + ttk.Label(frame, text="(Khusus untuk Pembeli - Meja 1-10)", font=("Arial", 7), foreground='gray').grid(row=6, column=1, sticky='w') + + ttk.Button(frame, text="Login", command=self.handle_login).grid(row=7, column=0, columnspan=2, pady=12) + + def handle_login(self): u = self.username_var.get().strip() @@ -1150,25 +1159,66 @@ class App: messagebox.showerror("Gagal", "Username atau password salah") return - # TAMBAHAN: Validasi nama pembeli untuk role pembeli + # VALIDASI UNTUK PEMBELI if user['role'] in ['pembeli', 'user']: customer_name = self.customer_name_var.get().strip() + customer_meja = self.customer_meja_var.get().strip() + + # Validasi nama if not customer_name: messagebox.showwarning("Nama Pembeli", "Pembeli harus mengisi Nama Lengkap!") return - # Simpan nama pembeli di session + + # Validasi nomor meja + if not customer_meja: + messagebox.showwarning("Nomor Meja", "Pembeli harus mengisi Nomor Meja!") + return + + # Validasi format nomor meja + try: + nomor_meja = int(customer_meja) + if nomor_meja < 1 or nomor_meja > 10: + messagebox.showwarning("Nomor Meja Invalid", "Nomor meja harus antara 1-10") + return + except ValueError: + messagebox.showerror("Input Error", "Nomor meja harus berupa angka!") + return + + # CEK APAKAH MEJA TERSEDIA + meja_data = meja_get(nomor_meja) + if meja_data: + status_meja = meja_data[1] + if status_meja == 'terisi': + messagebox.showwarning( + "Meja Tidak Tersedia", + f"❌ Maaf, Meja {nomor_meja} sedang terisi.\n\n" + f"Silakan pilih nomor meja lain." + ) + return + + # Simpan nama dan nomor meja di session user['customer_name'] = customer_name + user['nomor_meja'] = nomor_meja + + # RESERVASI MEJA (set status terisi tanpa transaksi_id dulu) + # Nanti saat checkout baru dikasih transaksi_id + meja_update_status(nomor_meja, 'terisi', '') + + # Pesan selamat datang khusus pembeli + welcome_msg = f"✅ Selamat datang, {customer_name}!\n\n" + welcome_msg += f"📍 Anda duduk di Meja {nomor_meja}\n\n" + welcome_msg += f"đŸŊī¸ Silakan pilih menu dan pesan makanan Anda.\n" + welcome_msg += f"Selamat menikmati!" + + messagebox.showinfo("Login Berhasil", welcome_msg) + else: + # Pesan untuk role lain (admin, kasir, waiter, pemilik) + messagebox.showinfo("Sukses", f"Login berhasil sebagai {user['role']}") self.session = user - - # Pesan selamat datang - welcome_msg = f"Login berhasil sebagai {user['role']}" - if user['role'] in ['pembeli', 'user']: - welcome_msg = f"Selamat datang, {user.get('customer_name')}!\n\nSilakan pilih menu dan tentukan nomor meja Anda." - - messagebox.showinfo("Sukses", welcome_msg) self.dashboard_frame() + def logout(self): self.session = None self.img_cache.clear() @@ -1244,35 +1294,32 @@ class App: # ========================================== - # ROLE: ADMIN (Kelola Semua) + # ROLE: ADMIN (Full Setup + Monitoring Read-Only) # ========================================== if self.session['role'] == 'admin': - # Kelola pesanan (akses waiter) - main.add(self.tab_waiter, text="đŸŊī¸ Kelola Pesanan") - - # Transaksi (akses kasir) - self.tab_payment = ttk.Frame(main) - main.add(self.tab_payment, text="💰 Transaksi") - - # Laporan (akses pemilik) - self.tab_report = ttk.Frame(main) - main.add(self.tab_report, text="📊 Laporan") - - self.tab_meja = ttk.Frame(main) - main.add(self.tab_meja, text="đŸĒ‘ Kelola Meja") - - # Kelola Menu & Promo (KHUSUS ADMIN) + # FULL ACCESS (CRUD) main.add(self.tab_menu_manage, text="âš™ī¸ Kelola Menu") main.add(self.tab_promo, text="🎁 Kelola Promo") - # Kelola User (TAMBAHAN NANTI) self.tab_user_manage = ttk.Frame(main) main.add(self.tab_user_manage, text="đŸ‘Ĩ Kelola User") + + self.tab_meja = ttk.Frame(main) + main.add(self.tab_meja, text="đŸĒ‘ Kelola Meja") + + # READ-ONLY MONITORING + main.add(self.tab_waiter, text="đŸ‘ī¸ Monitor Pesanan") # ← READ-ONLY + + self.tab_payment = ttk.Frame(main) + main.add(self.tab_payment, text="đŸ‘ī¸ Monitor Transaksi") # ← READ-ONLY + + self.tab_report = ttk.Frame(main) + main.add(self.tab_report, text="📊 Laporan") - # ======================================== - # BUILD TAB BERDASARKAN ROLE (REVISI) + # ======================================== + # BUILD TAB BERDASARKAN ROLE # ======================================== # Menu View - untuk SEMUA role @@ -1288,26 +1335,30 @@ class App: self.build_waiter_tab(self.tab_waiter) self.build_meja_tab(self.tab_meja) - # Kasir (Order + Transaksi SAJA) + # Kasir if self.session['role'] == 'kasir': self.build_order_tab(self.tab_order) self.build_payment_tab(self.tab_payment) self.build_meja_tab(self.tab_meja) - # Pemilik (Laporan SAJA) + # Pemilik if self.session['role'] == 'pemilik': self.build_report_tab(self.tab_report) - # Admin (Semua Akses) + # Admin - PERBAIKAN DI SINI! if self.session['role'] == 'admin': - self.build_order_tab(self.tab_order) - self.build_waiter_tab(self.tab_waiter) - self.build_payment_tab(self.tab_payment) - self.build_report_tab(self.tab_report) + # Master Data (Full CRUD) self.build_menu_manage_tab(self.tab_menu_manage) self.build_promo_tab(self.tab_promo) - self.build_user_manage_tab(self.tab_user_manage) # TAMBAHAN + self.build_user_manage_tab(self.tab_user_manage) self.build_meja_tab(self.tab_meja) + + # Monitoring (Read-Only) + self.build_waiter_tab(self.tab_waiter, readonly=True) + self.build_payment_tab(self.tab_payment, readonly=True) + self.build_report_tab(self.tab_report) + + def build_menu_view_tab(self, parent): @@ -1731,10 +1782,20 @@ class App: checkout_frame = ttk.Frame(right) checkout_frame.pack(fill='x', pady=6, padx=10) - # Row 0: No. Meja + # Row 0: No. Meja (AUTO-FILLED untuk pembeli) ← DIPERBAIKI ttk.Label(checkout_frame, text="No. Meja:", font=("Arial", 9)).grid(row=0, column=0, sticky='w', padx=3, pady=3) self.order_meja_var = tk.StringVar() - ttk.Entry(checkout_frame, textvariable=self.order_meja_var, width=20).grid(row=0, column=1, columnspan=2, pady=3, sticky='ew') + + # ✅ AUTO-FILL jika user adalah pembeli + if self.session['role'] in ['pembeli', 'user'] and 'nomor_meja' in self.session: + self.order_meja_var.set(str(self.session['nomor_meja'])) + meja_entry = ttk.Entry(checkout_frame, textvariable=self.order_meja_var, width=20, state='disabled') + # Tambah label info + ttk.Label(checkout_frame, text="✓", font=("Arial", 10), foreground='green').grid(row=0, column=2, padx=3) + else: + meja_entry = ttk.Entry(checkout_frame, textvariable=self.order_meja_var, width=20) + + meja_entry.grid(row=0, column=1, pady=3, sticky='ew') # Row 1: Kode Promo ttk.Label(checkout_frame, text="Kode Promo:", font=("Arial", 9)).grid(row=1, column=0, sticky='w', padx=3, pady=3) @@ -1762,6 +1823,7 @@ class App: # Load menu cards self.reload_order_menu_cards() + def reload_order_menu_cards(self): """Load menu dalam bentuk cards dengan gambar + tombol +/-""" # Clear existing cards @@ -2322,16 +2384,40 @@ class App: # Wilayah dikuasai Waiter - def build_waiter_tab(self, parent): - """Tab untuk waiter mengelola pesanan""" + def build_waiter_tab(self, parent, readonly=False): + """Tab untuk waiter mengelola pesanan + + Args: + parent: Parent frame + readonly: Jika True, tombol aksi disabled (untuk admin monitoring) + """ for w in parent.winfo_children(): w.destroy() # Header header = ttk.Frame(parent) header.pack(fill='x', padx=10, pady=6) - ttk.Label(header, text="Dashboard Waiter - Kelola Pesanan", font=("Arial", 13, "bold")).pack(side='left') - ttk.Button(header, text="🔄 Refresh", command=self.reload_waiter_orders).pack(side='right', padx=6) + + # ✅ UBAH JUDUL SESUAI MODE + if readonly: + ttk.Label( + header, + text="đŸ‘ī¸ Monitor Pesanan (Read-Only)", + font=("Arial", 13, "bold"), + foreground='orange' + ).pack(side='left') + else: + ttk.Label( + header, + text="Dashboard Waiter - Kelola Pesanan", + font=("Arial", 13, "bold") + ).pack(side='left') + + ttk.Button( + header, + text="🔄 Refresh", + command=self.reload_waiter_orders + ).pack(side='right', padx=6) # Filter status filter_frame = ttk.Frame(parent) @@ -2343,7 +2429,7 @@ class App: ttk.Button(filter_frame, text="Diproses", command=lambda: self.reload_waiter_orders('diproses'), width=8).pack(side='left', padx=2) ttk.Button(filter_frame, text="Selesai", command=lambda: self.reload_waiter_orders('selesai'), width=8).pack(side='left', padx=2) - # Treeview pesanan (LEBIH PENDEK) + # Treeview pesanan tree_frame = ttk.Frame(parent) tree_frame.pack(fill='x', padx=10, pady=4) @@ -2366,70 +2452,93 @@ class App: self.waiter_tree.column("Promo", width=80) self.waiter_tree.column("Tanggal", width=140) - # Scrollbar untuk tree tree_scroll = ttk.Scrollbar(tree_frame, orient='vertical', command=self.waiter_tree.yview) self.waiter_tree.configure(yscrollcommand=tree_scroll.set) self.waiter_tree.pack(side='left', fill='both', expand=True) tree_scroll.pack(side='right', fill='y') - # Bind event saat pilih pesanan self.waiter_tree.bind("<>", self.on_waiter_select) - # Detail pesanan (LEBIH PENDEK - height=6 aja) + # Detail pesanan detail_frame = ttk.LabelFrame(parent, text="📋 Detail Pesanan", padding=8) detail_frame.pack(fill='x', padx=10, pady=4) - # Frame untuk text + scrollbar text_frame = ttk.Frame(detail_frame) text_frame.pack(fill='both', expand=True) - self.waiter_detail_text = tk.Text(text_frame, height=6, width=100, font=("Courier New", 8), wrap='word') + self.waiter_detail_text = tk.Text( + text_frame, + height=6, + width=100, + font=("Courier New", 8), + wrap='word' + ) detail_scroll = ttk.Scrollbar(text_frame, orient='vertical', command=self.waiter_detail_text.yview) self.waiter_detail_text.configure(yscrollcommand=detail_scroll.set) self.waiter_detail_text.pack(side='left', fill='both', expand=True) detail_scroll.pack(side='right', fill='y') - # TOMBOL AKSI - PASTI KELIHATAN (TIDAK PAKAI expand=True) + # TOMBOL AKSI action_frame = ttk.LabelFrame(parent, text="đŸŽ¯ Ubah Status Pesanan", padding=12) action_frame.pack(fill='x', padx=10, pady=8) + # ✅ JIKA READ-ONLY, TAMPILKAN WARNING & DISABLE TOMBOL + if readonly: + warning_label = ttk.Label( + action_frame, + text="âš ī¸ MODE READ-ONLY: Anda hanya bisa melihat data, tidak bisa mengubah status.\n" + "Hanya Waiter yang bisa mengubah status pesanan.", + font=("Arial", 9), + foreground='red', + justify='center' + ) + warning_label.pack(pady=10) + # Grid 2x2 untuk 4 tombol - ttk.Button( - action_frame, - text="✅ Terima (Pending → Menunggu)", - command=lambda: self.update_order_status('menunggu'), - width=35 - ).grid(row=0, column=0, padx=4, pady=4, sticky='ew') + btn1 = ttk.Button( + action_frame, + text="✅ Terima (Pending → Menunggu)", + command=lambda: self.update_order_status('menunggu') if not readonly else None, + width=35, + state='disabled' if readonly else 'normal' + ) + btn1.grid(row=0, column=0, padx=4, pady=4, sticky='ew') - ttk.Button( - action_frame, - text="đŸŗ Proses (Menunggu → Diproses)", - command=lambda: self.update_order_status('diproses'), - width=35 - ).grid(row=0, column=1, padx=4, pady=4, sticky='ew') + btn2 = ttk.Button( + action_frame, + text="đŸŗ Proses (Menunggu → Diproses)", + command=lambda: self.update_order_status('diproses') if not readonly else None, + width=35, + state='disabled' if readonly else 'normal' + ) + btn2.grid(row=0, column=1, padx=4, pady=4, sticky='ew') - ttk.Button( - action_frame, - text="đŸŊī¸ Selesai (Diproses → Selesai)", - command=lambda: self.update_order_status('selesai'), - width=35 - ).grid(row=1, column=0, padx=4, pady=4, sticky='ew') + btn3 = ttk.Button( + action_frame, + text="đŸŊī¸ Selesai (Diproses → Selesai)", + command=lambda: self.update_order_status('selesai') if not readonly else None, + width=35, + state='disabled' if readonly else 'normal' + ) + btn3.grid(row=1, column=0, padx=4, pady=4, sticky='ew') - ttk.Button( - action_frame, - text="💰 Dibayar (Selesai → Dibayar)", - command=lambda: self.update_order_status('dibayar'), - width=35 - ).grid(row=1, column=1, padx=4, pady=4, sticky='ew') + btn4 = ttk.Button( + action_frame, + text="💰 Dibayar (Selesai → Dibayar)", + command=lambda: self.update_order_status('dibayar') if not readonly else None, + width=35, + state='disabled' if readonly else 'normal' + ) + btn4.grid(row=1, column=1, padx=4, pady=4, sticky='ew') - # Buat kolom responsive action_frame.columnconfigure(0, weight=1) action_frame.columnconfigure(1, weight=1) # Load data self.reload_waiter_orders() + def reload_waiter_orders(self, status_filter=None): """Load pesanan untuk waiter""" @@ -2808,26 +2917,71 @@ class App: return meja_update_status(nomor_meja, "terisi", transaksi_id) - def build_payment_tab(self, parent): - """Tab pembayaran untuk kasir & admin""" + def build_payment_tab(self, parent, readonly=False): + """Tab pembayaran untuk kasir & admin + + Args: + parent: Parent frame + readonly: Jika True, form disabled (untuk admin monitoring) + """ for w in parent.winfo_children(): w.destroy() # Header header = ttk.Frame(parent) header.pack(fill='x', padx=10, pady=8) - ttk.Label(header, text="💰 Pembayaran Transaksi", font=("Arial", 14, "bold")).pack(side='left') - ttk.Button(header, text="🔄 Refresh", command=self.reload_payment_orders).pack(side='right', padx=6) - # ===== TAMBAHAN: SUMMARY PENJUALAN HARI INI UNTUK KASIR ===== - if self.session['role'] == 'kasir': + # ✅ UBAH JUDUL SESUAI MODE + if readonly: + ttk.Label( + header, + text="đŸ‘ī¸ Monitor Transaksi (Read-Only)", + font=("Arial", 14, "bold"), + foreground='orange' + ).pack(side='left') + else: + ttk.Label( + header, + text="💰 Pembayaran Transaksi", + font=("Arial", 14, "bold") + ).pack(side='left') + + ttk.Button( + header, + text="🔄 Refresh", + command=self.reload_payment_orders + ).pack(side='right', padx=6) + + # Search by nomor meja (TETAP AKTIF untuk admin) + search_frame = ttk.LabelFrame(parent, text="🔍 Cari Transaksi by Nomor Meja", padding=10) + search_frame.pack(fill='x', padx=10, pady=6) + + search_inner = ttk.Frame(search_frame) + search_inner.pack() + + ttk.Label(search_inner, text="Nomor Meja:", font=("Arial", 9)).grid(row=0, column=0, padx=5) + self.search_meja_var = tk.StringVar() + ttk.Entry(search_inner, textvariable=self.search_meja_var, width=15).grid(row=0, column=1, padx=5) + ttk.Button( + search_inner, + text="🔍 Cari Tagihan", + command=self.search_by_meja, + style="Accent.TButton" + ).grid(row=0, column=2, padx=5) + ttk.Button( + search_inner, + text="🔄 Tampilkan Semua", + command=self.reload_payment_orders + ).grid(row=0, column=3, padx=5) + + # Summary penjualan hari ini (untuk kasir & admin) + if self.session['role'] in ['kasir', 'admin']: summary_frame = ttk.LabelFrame(parent, text="📊 Penjualan Hari Ini", padding=10) summary_frame.pack(fill='x', padx=10, pady=6) summary_inner = ttk.Frame(summary_frame) summary_inner.pack() - # Hitung total hari ini from datetime import datetime today_income = 0 today_count = 0 @@ -2851,22 +3005,14 @@ class App: ttk.Label(summary_inner, text="Total Pendapatan Hari Ini:", font=("Arial", 10)).grid(row=1, column=0, sticky='w', padx=10, pady=3) ttk.Label(summary_inner, text=f"Rp {today_income:,.0f}", font=("Arial", 10, "bold"), foreground='green').grid(row=1, column=1, sticky='w', padx=10, pady=3) - - # Container utama - main_container = ttk.Frame(parent) - main_container.pack(fill='both', expand=True, padx=10, pady=6) - ttk.Label(header, text="💰 Pembayaran Transaksi", font=("Arial", 14, "bold")).pack(side='left') - ttk.Button(header, text="🔄 Refresh", command=self.reload_payment_orders).pack(side='right', padx=6) - # Container utama main_container = ttk.Frame(parent) main_container.pack(fill='both', expand=True, padx=10, pady=6) - # === PANEL KIRI: Daftar Transaksi === + # Panel Kiri: Daftar Transaksi left = ttk.LabelFrame(main_container, text="📋 Transaksi Siap Dibayar (Status: Selesai)", padding=10) left.pack(side='left', fill='both', expand=True, padx=(0, 5)) - # Treeview transaksi dengan scrollbar tree_frame = ttk.Frame(left) tree_frame.pack(fill='both', expand=True) @@ -2874,7 +3020,13 @@ class App: tree_scroll.pack(side='right', fill='y') cols = ("ID", "Meja", "Total", "Status", "Tanggal") - self.payment_tree = ttk.Treeview(tree_frame, columns=cols, show='headings', height=10, yscrollcommand=tree_scroll.set) + self.payment_tree = ttk.Treeview( + tree_frame, + columns=cols, + show='headings', + height=10, + yscrollcommand=tree_scroll.set + ) tree_scroll.config(command=self.payment_tree.yview) @@ -2906,95 +3058,186 @@ class App: self.payment_detail_text.pack(side='left', fill='both', expand=True) detail_scroll.pack(side='right', fill='y') - # === PANEL KANAN: Form Pembayaran === - right_outer = ttk.LabelFrame(main_container, text="đŸ’ŗ Form Pembayaran", padding=5) - right_outer.pack(side='right', fill='both', expand=True, padx=(5, 0)) - - # Frame wrapper untuk canvas - wrapper = ttk.Frame(right_outer) - wrapper.pack(fill='both', expand=True) - - # Scrollbar - scrollbar = ttk.Scrollbar(wrapper, orient='vertical') - scrollbar.pack(side='right', fill='y') - - # Canvas dengan HEIGHT FIXED - canvas = tk.Canvas(wrapper, yscrollcommand=scrollbar.set, highlightthickness=0, height=450) - canvas.pack(side='left', fill='both', expand=True) - - scrollbar.config(command=canvas.yview) - - # Frame konten - right = ttk.Frame(canvas) - canvas_window = canvas.create_window((0, 0), window=right, anchor='nw') - - # Update scroll region - def configure_scroll(event): - canvas.configure(scrollregion=canvas.bbox('all')) - - right.bind('', configure_scroll) - - # Update width - def configure_canvas(event): - canvas.itemconfig(canvas_window, width=event.width) - - canvas.bind('', configure_canvas) - - # Mouse wheel - def on_mousewheel(event): - canvas.yview_scroll(-1 * int(event.delta / 120), "units") - - canvas.bind_all('', on_mousewheel) - - # === ISI FORM === - - # Info transaksi - info_frame = ttk.Frame(right) - info_frame.pack(fill='x', pady=15, padx=15) - - self.selected_transaksi_label = ttk.Label(info_frame, text="Belum ada transaksi dipilih", font=("Arial", 10, "bold"), foreground='red') - self.selected_transaksi_label.pack() - - self.selected_total_label = ttk.Label(info_frame, text="Total: Rp 0", font=("Arial", 12, "bold"), foreground='green') - self.selected_total_label.pack(pady=4) - - ttk.Separator(right, orient='horizontal').pack(fill='x', pady=15, padx=15) - - # Metode pembayaran - method_frame = ttk.Frame(right) - method_frame.pack(fill='x', pady=10, padx=15) - - ttk.Label(method_frame, text="đŸ’ŗ Pilih Metode Pembayaran:", font=("Arial", 10, "bold")).pack(anchor='w', pady=6) - - self.payment_method_var = tk.StringVar(value='cash') - - ttk.Radiobutton(method_frame, text="đŸ’ĩ Cash", variable=self.payment_method_var, value='cash', command=self.on_payment_method_change).pack(anchor='w', pady=3) - ttk.Radiobutton(method_frame, text="📱 QRIS", variable=self.payment_method_var, value='qris', command=self.on_payment_method_change).pack(anchor='w', pady=3) - ttk.Radiobutton(method_frame, text="đŸ’ŗ E-Wallet (GoPay/OVO/Dana)", variable=self.payment_method_var, value='ewallet', command=self.on_payment_method_change).pack(anchor='w', pady=3) - - ttk.Separator(right, orient='horizontal').pack(fill='x', pady=15, padx=15) - - # Frame input dinamis - self.payment_input_frame = ttk.Frame(right) - self.payment_input_frame.pack(fill='x', pady=10, padx=15) - - self.build_cash_input() - - ttk.Separator(right, orient='horizontal').pack(fill='x', pady=15, padx=15) - - # Tombol - btn_frame = ttk.Frame(right) - btn_frame.pack(fill='x', pady=20, padx=15) - - ttk.Button(btn_frame, text="✅ PROSES PEMBAYARAN", command=self.process_payment, style="Accent.TButton").pack(ipadx=30, ipady=10) - - # PENTING: Tambah banyak space di bawah biar scrollbar muncul - for i in range(20): - ttk.Label(right, text="").pack() + # ✅ JIKA READ-ONLY, HANYA TAMPILKAN INFO, TANPA FORM PEMBAYARAN + if readonly: + readonly_frame = ttk.LabelFrame(main_container, text="â„šī¸ Informasi", padding=20) + readonly_frame.pack(side='right', fill='both', expand=True, padx=(5, 0)) + + warning_text = ( + "âš ī¸ MODE READ-ONLY\n\n" + "Anda hanya bisa melihat data transaksi.\n\n" + "Untuk memproses pembayaran, silakan login sebagai KASIR.\n\n" + "Admin hanya bisa monitoring transaksi untuk audit dan kontrol." + ) + + ttk.Label( + readonly_frame, + text=warning_text, + font=("Arial", 10), + foreground='red', + justify='center' + ).pack(expand=True) + else: + # Panel Kanan: Form Pembayaran (HANYA UNTUK KASIR) + right_outer = ttk.LabelFrame(main_container, text="đŸ’ŗ Form Pembayaran", padding=5) + right_outer.pack(side='right', fill='both', expand=True, padx=(5, 0)) + + wrapper = ttk.Frame(right_outer) + wrapper.pack(fill='both', expand=True) + + scrollbar = ttk.Scrollbar(wrapper, orient='vertical') + scrollbar.pack(side='right', fill='y') + + canvas = tk.Canvas(wrapper, yscrollcommand=scrollbar.set, highlightthickness=0, height=450) + canvas.pack(side='left', fill='both', expand=True) + + scrollbar.config(command=canvas.yview) + + right = ttk.Frame(canvas) + canvas_window = canvas.create_window((0, 0), window=right, anchor='nw') + + def configure_scroll(event): + canvas.configure(scrollregion=canvas.bbox('all')) + + right.bind('', configure_scroll) + + def configure_canvas(event): + canvas.itemconfig(canvas_window, width=event.width) + + canvas.bind('', configure_canvas) + + def on_mousewheel(event): + canvas.yview_scroll(-1 * int(event.delta / 120), "units") + + canvas.bind_all('', on_mousewheel) + + # Info transaksi + info_frame = ttk.Frame(right) + info_frame.pack(fill='x', pady=15, padx=15) + + self.selected_transaksi_label = ttk.Label( + info_frame, + text="Belum ada transaksi dipilih", + font=("Arial", 10, "bold"), + foreground='red' + ) + self.selected_transaksi_label.pack() + + self.selected_total_label = ttk.Label( + info_frame, + text="Total: Rp 0", + font=("Arial", 12, "bold"), + foreground='green' + ) + self.selected_total_label.pack(pady=4) + + ttk.Separator(right, orient='horizontal').pack(fill='x', pady=15, padx=15) + + # Metode pembayaran + method_frame = ttk.Frame(right) + method_frame.pack(fill='x', pady=10, padx=15) + + ttk.Label(method_frame, text="đŸ’ŗ Pilih Metode Pembayaran:", font=("Arial", 10, "bold")).pack(anchor='w', pady=6) + + self.payment_method_var = tk.StringVar(value='cash') + + ttk.Radiobutton(method_frame, text="đŸ’ĩ Cash", variable=self.payment_method_var, value='cash', command=self.on_payment_method_change).pack(anchor='w', pady=3) + ttk.Radiobutton(method_frame, text="📱 QRIS", variable=self.payment_method_var, value='qris', command=self.on_payment_method_change).pack(anchor='w', pady=3) + ttk.Radiobutton(method_frame, text="đŸ’ŗ E-Wallet (GoPay/OVO/Dana)", variable=self.payment_method_var, value='ewallet', command=self.on_payment_method_change).pack(anchor='w', pady=3) + + ttk.Separator(right, orient='horizontal').pack(fill='x', pady=15, padx=15) + + # Frame input dinamis + self.payment_input_frame = ttk.Frame(right) + self.payment_input_frame.pack(fill='x', pady=10, padx=15) + + self.build_cash_input() + + ttk.Separator(right, orient='horizontal').pack(fill='x', pady=15, padx=15) + + # Tombol + btn_frame = ttk.Frame(right) + btn_frame.pack(fill='x', pady=20, padx=15) + + ttk.Button( + btn_frame, + text="✅ PROSES PEMBAYARAN", + command=self.process_payment, + style="Accent.TButton" + ).pack(ipadx=30, ipady=10) + + for i in range(20): + ttk.Label(right, text="").pack() # Load data self.reload_payment_orders() + + def search_by_meja(self): + """Cari transaksi berdasarkan nomor meja - FITUR BARU!""" + nomor_meja = self.search_meja_var.get().strip() + + if not nomor_meja: + messagebox.showwarning("Input Error", "Masukkan nomor meja") + return + + try: + nomor_meja = int(nomor_meja) + except: + messagebox.showerror("Input Error", "Nomor meja harus angka") + return + + # Validasi range meja + if nomor_meja < 1 or nomor_meja > 10: + messagebox.showwarning("Invalid", "Nomor meja harus 1-10") + return + + # Clear tree + for r in self.payment_tree.get_children(): + self.payment_tree.delete(r) + + # Get transaksi selesai untuk meja ini + orders = transaksi_list(status='selesai') + + found = False + for order in orders: + tid, uid, meja, total, status, promo_code, tanggal = order + + # Filter by nomor meja + if meja != nomor_meja: + continue + + # Cek belum dibayar + payment_data = pembayaran_get_by_transaksi(tid) + if payment_data: + continue # Skip yang sudah dibayar + + self.payment_tree.insert( + "", + tk.END, + values=(tid, meja, f"Rp {total:,.0f}", status, tanggal) + ) + found = True + + if not found: + messagebox.showinfo( + "Tidak Ditemukan", + f"❌ Tidak ada tagihan aktif untuk Meja {nomor_meja}\n\n" + f"Kemungkinan:\n" + f"â€ĸ Meja belum pesan atau pesanan belum selesai\n" + f"â€ĸ Tagihan sudah dibayar\n" + f"â€ĸ Meja tidak ada pesanan" + ) + + # Tampilkan semua transaksi lagi + self.reload_payment_orders() + else: + messagebox.showinfo( + "✅ Ditemukan", + f"Tagihan untuk Meja {nomor_meja} berhasil ditemukan!\n\n" + f"Silakan pilih transaksi dan proses pembayaran." + ) + def build_report_tab(self, parent): """Tab laporan penjualan untuk admin/pemilik""" for w in parent.winfo_children(): diff --git a/meja.csv b/meja.csv index 2fe4157..3e43136 100644 --- a/meja.csv +++ b/meja.csv @@ -1,6 +1,6 @@ nomor_meja,status,transaksi_id 1,kosong, -2,kosong, +2,terisi, 3,kosong, 4,kosong, 5,kosong,