This commit is contained in:
Jevinca Marvella 2025-12-14 09:37:44 +07:00
parent 2d9c0fc08d
commit 63e0be5b10
2 changed files with 421 additions and 178 deletions

597
main.py
View File

@ -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("<<TreeviewSelect>>", 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>', configure_scroll)
# Update width
def configure_canvas(event):
canvas.itemconfig(canvas_window, width=event.width)
canvas.bind('<Configure>', configure_canvas)
# Mouse wheel
def on_mousewheel(event):
canvas.yview_scroll(-1 * int(event.delta / 120), "units")
canvas.bind_all('<MouseWheel>', 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>', configure_scroll)
def configure_canvas(event):
canvas.itemconfig(canvas_window, width=event.width)
canvas.bind('<Configure>', configure_canvas)
def on_mousewheel(event):
canvas.yview_scroll(-1 * int(event.delta / 120), "units")
canvas.bind_all('<MouseWheel>', 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():

View File

@ -1,6 +1,6 @@
nomor_meja,status,transaksi_id
1,kosong,
2,kosong,
2,terisi,
3,kosong,
4,kosong,
5,kosong,

1 nomor_meja status transaksi_id
2 1 kosong
3 2 kosong terisi
4 3 kosong
5 4 kosong
6 5 kosong