Revisi
This commit is contained in:
parent
2d9c0fc08d
commit
63e0be5b10
597
main.py
597
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("<<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():
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user