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)
|
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.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):
|
def handle_login(self):
|
||||||
u = self.username_var.get().strip()
|
u = self.username_var.get().strip()
|
||||||
@ -1150,25 +1159,66 @@ class App:
|
|||||||
messagebox.showerror("Gagal", "Username atau password salah")
|
messagebox.showerror("Gagal", "Username atau password salah")
|
||||||
return
|
return
|
||||||
|
|
||||||
# TAMBAHAN: Validasi nama pembeli untuk role pembeli
|
# VALIDASI UNTUK PEMBELI
|
||||||
if user['role'] in ['pembeli', 'user']:
|
if user['role'] in ['pembeli', 'user']:
|
||||||
customer_name = self.customer_name_var.get().strip()
|
customer_name = self.customer_name_var.get().strip()
|
||||||
|
customer_meja = self.customer_meja_var.get().strip()
|
||||||
|
|
||||||
|
# Validasi nama
|
||||||
if not customer_name:
|
if not customer_name:
|
||||||
messagebox.showwarning("Nama Pembeli", "Pembeli harus mengisi Nama Lengkap!")
|
messagebox.showwarning("Nama Pembeli", "Pembeli harus mengisi Nama Lengkap!")
|
||||||
return
|
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['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
|
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()
|
self.dashboard_frame()
|
||||||
|
|
||||||
|
|
||||||
def logout(self):
|
def logout(self):
|
||||||
self.session = None
|
self.session = None
|
||||||
self.img_cache.clear()
|
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':
|
if self.session['role'] == 'admin':
|
||||||
|
|
||||||
# Kelola pesanan (akses waiter)
|
# FULL ACCESS (CRUD)
|
||||||
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)
|
|
||||||
main.add(self.tab_menu_manage, text="⚙️ Kelola Menu")
|
main.add(self.tab_menu_manage, text="⚙️ Kelola Menu")
|
||||||
main.add(self.tab_promo, text="🎁 Kelola Promo")
|
main.add(self.tab_promo, text="🎁 Kelola Promo")
|
||||||
|
|
||||||
# Kelola User (TAMBAHAN NANTI)
|
|
||||||
self.tab_user_manage = ttk.Frame(main)
|
self.tab_user_manage = ttk.Frame(main)
|
||||||
main.add(self.tab_user_manage, text="👥 Kelola User")
|
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
|
# Menu View - untuk SEMUA role
|
||||||
@ -1288,26 +1335,30 @@ class App:
|
|||||||
self.build_waiter_tab(self.tab_waiter)
|
self.build_waiter_tab(self.tab_waiter)
|
||||||
self.build_meja_tab(self.tab_meja)
|
self.build_meja_tab(self.tab_meja)
|
||||||
|
|
||||||
# Kasir (Order + Transaksi SAJA)
|
# Kasir
|
||||||
if self.session['role'] == 'kasir':
|
if self.session['role'] == 'kasir':
|
||||||
self.build_order_tab(self.tab_order)
|
self.build_order_tab(self.tab_order)
|
||||||
self.build_payment_tab(self.tab_payment)
|
self.build_payment_tab(self.tab_payment)
|
||||||
self.build_meja_tab(self.tab_meja)
|
self.build_meja_tab(self.tab_meja)
|
||||||
|
|
||||||
# Pemilik (Laporan SAJA)
|
# Pemilik
|
||||||
if self.session['role'] == 'pemilik':
|
if self.session['role'] == 'pemilik':
|
||||||
self.build_report_tab(self.tab_report)
|
self.build_report_tab(self.tab_report)
|
||||||
|
|
||||||
# Admin (Semua Akses)
|
# Admin - PERBAIKAN DI SINI!
|
||||||
if self.session['role'] == 'admin':
|
if self.session['role'] == 'admin':
|
||||||
self.build_order_tab(self.tab_order)
|
# Master Data (Full CRUD)
|
||||||
self.build_waiter_tab(self.tab_waiter)
|
|
||||||
self.build_payment_tab(self.tab_payment)
|
|
||||||
self.build_report_tab(self.tab_report)
|
|
||||||
self.build_menu_manage_tab(self.tab_menu_manage)
|
self.build_menu_manage_tab(self.tab_menu_manage)
|
||||||
self.build_promo_tab(self.tab_promo)
|
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)
|
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):
|
def build_menu_view_tab(self, parent):
|
||||||
@ -1731,10 +1782,20 @@ class App:
|
|||||||
checkout_frame = ttk.Frame(right)
|
checkout_frame = ttk.Frame(right)
|
||||||
checkout_frame.pack(fill='x', pady=6, padx=10)
|
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)
|
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()
|
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
|
# 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)
|
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
|
# Load menu cards
|
||||||
self.reload_order_menu_cards()
|
self.reload_order_menu_cards()
|
||||||
|
|
||||||
|
|
||||||
def reload_order_menu_cards(self):
|
def reload_order_menu_cards(self):
|
||||||
"""Load menu dalam bentuk cards dengan gambar + tombol +/-"""
|
"""Load menu dalam bentuk cards dengan gambar + tombol +/-"""
|
||||||
# Clear existing cards
|
# Clear existing cards
|
||||||
@ -2322,16 +2384,40 @@ class App:
|
|||||||
# Wilayah dikuasai Waiter
|
# Wilayah dikuasai Waiter
|
||||||
|
|
||||||
|
|
||||||
def build_waiter_tab(self, parent):
|
def build_waiter_tab(self, parent, readonly=False):
|
||||||
"""Tab untuk waiter mengelola pesanan"""
|
"""Tab untuk waiter mengelola pesanan
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent: Parent frame
|
||||||
|
readonly: Jika True, tombol aksi disabled (untuk admin monitoring)
|
||||||
|
"""
|
||||||
for w in parent.winfo_children():
|
for w in parent.winfo_children():
|
||||||
w.destroy()
|
w.destroy()
|
||||||
|
|
||||||
# Header
|
# Header
|
||||||
header = ttk.Frame(parent)
|
header = ttk.Frame(parent)
|
||||||
header.pack(fill='x', padx=10, pady=6)
|
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 status
|
||||||
filter_frame = ttk.Frame(parent)
|
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="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)
|
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 = ttk.Frame(parent)
|
||||||
tree_frame.pack(fill='x', padx=10, pady=4)
|
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("Promo", width=80)
|
||||||
self.waiter_tree.column("Tanggal", width=140)
|
self.waiter_tree.column("Tanggal", width=140)
|
||||||
|
|
||||||
# Scrollbar untuk tree
|
|
||||||
tree_scroll = ttk.Scrollbar(tree_frame, orient='vertical', command=self.waiter_tree.yview)
|
tree_scroll = ttk.Scrollbar(tree_frame, orient='vertical', command=self.waiter_tree.yview)
|
||||||
self.waiter_tree.configure(yscrollcommand=tree_scroll.set)
|
self.waiter_tree.configure(yscrollcommand=tree_scroll.set)
|
||||||
|
|
||||||
self.waiter_tree.pack(side='left', fill='both', expand=True)
|
self.waiter_tree.pack(side='left', fill='both', expand=True)
|
||||||
tree_scroll.pack(side='right', fill='y')
|
tree_scroll.pack(side='right', fill='y')
|
||||||
|
|
||||||
# Bind event saat pilih pesanan
|
|
||||||
self.waiter_tree.bind("<<TreeviewSelect>>", self.on_waiter_select)
|
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 = ttk.LabelFrame(parent, text="📋 Detail Pesanan", padding=8)
|
||||||
detail_frame.pack(fill='x', padx=10, pady=4)
|
detail_frame.pack(fill='x', padx=10, pady=4)
|
||||||
|
|
||||||
# Frame untuk text + scrollbar
|
|
||||||
text_frame = ttk.Frame(detail_frame)
|
text_frame = ttk.Frame(detail_frame)
|
||||||
text_frame.pack(fill='both', expand=True)
|
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)
|
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.configure(yscrollcommand=detail_scroll.set)
|
||||||
|
|
||||||
self.waiter_detail_text.pack(side='left', fill='both', expand=True)
|
self.waiter_detail_text.pack(side='left', fill='both', expand=True)
|
||||||
detail_scroll.pack(side='right', fill='y')
|
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 = ttk.LabelFrame(parent, text="🎯 Ubah Status Pesanan", padding=12)
|
||||||
action_frame.pack(fill='x', padx=10, pady=8)
|
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
|
# Grid 2x2 untuk 4 tombol
|
||||||
ttk.Button(
|
btn1 = ttk.Button(
|
||||||
action_frame,
|
action_frame,
|
||||||
text="✅ Terima (Pending → Menunggu)",
|
text="✅ Terima (Pending → Menunggu)",
|
||||||
command=lambda: self.update_order_status('menunggu'),
|
command=lambda: self.update_order_status('menunggu') if not readonly else None,
|
||||||
width=35
|
width=35,
|
||||||
).grid(row=0, column=0, padx=4, pady=4, sticky='ew')
|
state='disabled' if readonly else 'normal'
|
||||||
|
)
|
||||||
|
btn1.grid(row=0, column=0, padx=4, pady=4, sticky='ew')
|
||||||
|
|
||||||
ttk.Button(
|
btn2 = ttk.Button(
|
||||||
action_frame,
|
action_frame,
|
||||||
text="🍳 Proses (Menunggu → Diproses)",
|
text="🍳 Proses (Menunggu → Diproses)",
|
||||||
command=lambda: self.update_order_status('diproses'),
|
command=lambda: self.update_order_status('diproses') if not readonly else None,
|
||||||
width=35
|
width=35,
|
||||||
).grid(row=0, column=1, padx=4, pady=4, sticky='ew')
|
state='disabled' if readonly else 'normal'
|
||||||
|
)
|
||||||
|
btn2.grid(row=0, column=1, padx=4, pady=4, sticky='ew')
|
||||||
|
|
||||||
ttk.Button(
|
btn3 = ttk.Button(
|
||||||
action_frame,
|
action_frame,
|
||||||
text="🍽️ Selesai (Diproses → Selesai)",
|
text="🍽️ Selesai (Diproses → Selesai)",
|
||||||
command=lambda: self.update_order_status('selesai'),
|
command=lambda: self.update_order_status('selesai') if not readonly else None,
|
||||||
width=35
|
width=35,
|
||||||
).grid(row=1, column=0, padx=4, pady=4, sticky='ew')
|
state='disabled' if readonly else 'normal'
|
||||||
|
)
|
||||||
|
btn3.grid(row=1, column=0, padx=4, pady=4, sticky='ew')
|
||||||
|
|
||||||
ttk.Button(
|
btn4 = ttk.Button(
|
||||||
action_frame,
|
action_frame,
|
||||||
text="💰 Dibayar (Selesai → Dibayar)",
|
text="💰 Dibayar (Selesai → Dibayar)",
|
||||||
command=lambda: self.update_order_status('dibayar'),
|
command=lambda: self.update_order_status('dibayar') if not readonly else None,
|
||||||
width=35
|
width=35,
|
||||||
).grid(row=1, column=1, padx=4, pady=4, sticky='ew')
|
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(0, weight=1)
|
||||||
action_frame.columnconfigure(1, weight=1)
|
action_frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
# Load data
|
# Load data
|
||||||
self.reload_waiter_orders()
|
self.reload_waiter_orders()
|
||||||
|
|
||||||
|
|
||||||
def reload_waiter_orders(self, status_filter=None):
|
def reload_waiter_orders(self, status_filter=None):
|
||||||
"""Load pesanan untuk waiter"""
|
"""Load pesanan untuk waiter"""
|
||||||
@ -2808,26 +2917,71 @@ class App:
|
|||||||
return meja_update_status(nomor_meja, "terisi", transaksi_id)
|
return meja_update_status(nomor_meja, "terisi", transaksi_id)
|
||||||
|
|
||||||
|
|
||||||
def build_payment_tab(self, parent):
|
def build_payment_tab(self, parent, readonly=False):
|
||||||
"""Tab pembayaran untuk kasir & admin"""
|
"""Tab pembayaran untuk kasir & admin
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent: Parent frame
|
||||||
|
readonly: Jika True, form disabled (untuk admin monitoring)
|
||||||
|
"""
|
||||||
for w in parent.winfo_children():
|
for w in parent.winfo_children():
|
||||||
w.destroy()
|
w.destroy()
|
||||||
|
|
||||||
# Header
|
# Header
|
||||||
header = ttk.Frame(parent)
|
header = ttk.Frame(parent)
|
||||||
header.pack(fill='x', padx=10, pady=8)
|
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 =====
|
# ✅ UBAH JUDUL SESUAI MODE
|
||||||
if self.session['role'] == 'kasir':
|
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 = ttk.LabelFrame(parent, text="📊 Penjualan Hari Ini", padding=10)
|
||||||
summary_frame.pack(fill='x', padx=10, pady=6)
|
summary_frame.pack(fill='x', padx=10, pady=6)
|
||||||
|
|
||||||
summary_inner = ttk.Frame(summary_frame)
|
summary_inner = ttk.Frame(summary_frame)
|
||||||
summary_inner.pack()
|
summary_inner.pack()
|
||||||
|
|
||||||
# Hitung total hari ini
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
today_income = 0
|
today_income = 0
|
||||||
today_count = 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="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)
|
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
|
# Container utama
|
||||||
main_container = ttk.Frame(parent)
|
main_container = ttk.Frame(parent)
|
||||||
main_container.pack(fill='both', expand=True, padx=10, pady=6)
|
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 = ttk.LabelFrame(main_container, text="📋 Transaksi Siap Dibayar (Status: Selesai)", padding=10)
|
||||||
left.pack(side='left', fill='both', expand=True, padx=(0, 5))
|
left.pack(side='left', fill='both', expand=True, padx=(0, 5))
|
||||||
|
|
||||||
# Treeview transaksi dengan scrollbar
|
|
||||||
tree_frame = ttk.Frame(left)
|
tree_frame = ttk.Frame(left)
|
||||||
tree_frame.pack(fill='both', expand=True)
|
tree_frame.pack(fill='both', expand=True)
|
||||||
|
|
||||||
@ -2874,7 +3020,13 @@ class App:
|
|||||||
tree_scroll.pack(side='right', fill='y')
|
tree_scroll.pack(side='right', fill='y')
|
||||||
|
|
||||||
cols = ("ID", "Meja", "Total", "Status", "Tanggal")
|
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)
|
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)
|
self.payment_detail_text.pack(side='left', fill='both', expand=True)
|
||||||
detail_scroll.pack(side='right', fill='y')
|
detail_scroll.pack(side='right', fill='y')
|
||||||
|
|
||||||
# === PANEL KANAN: Form Pembayaran ===
|
# ✅ JIKA READ-ONLY, HANYA TAMPILKAN INFO, TANPA FORM PEMBAYARAN
|
||||||
right_outer = ttk.LabelFrame(main_container, text="💳 Form Pembayaran", padding=5)
|
if readonly:
|
||||||
right_outer.pack(side='right', fill='both', expand=True, padx=(5, 0))
|
readonly_frame = ttk.LabelFrame(main_container, text="ℹ️ Informasi", padding=20)
|
||||||
|
readonly_frame.pack(side='right', fill='both', expand=True, padx=(5, 0))
|
||||||
# Frame wrapper untuk canvas
|
|
||||||
wrapper = ttk.Frame(right_outer)
|
warning_text = (
|
||||||
wrapper.pack(fill='both', expand=True)
|
"⚠️ MODE READ-ONLY\n\n"
|
||||||
|
"Anda hanya bisa melihat data transaksi.\n\n"
|
||||||
# Scrollbar
|
"Untuk memproses pembayaran, silakan login sebagai KASIR.\n\n"
|
||||||
scrollbar = ttk.Scrollbar(wrapper, orient='vertical')
|
"Admin hanya bisa monitoring transaksi untuk audit dan kontrol."
|
||||||
scrollbar.pack(side='right', fill='y')
|
)
|
||||||
|
|
||||||
# Canvas dengan HEIGHT FIXED
|
ttk.Label(
|
||||||
canvas = tk.Canvas(wrapper, yscrollcommand=scrollbar.set, highlightthickness=0, height=450)
|
readonly_frame,
|
||||||
canvas.pack(side='left', fill='both', expand=True)
|
text=warning_text,
|
||||||
|
font=("Arial", 10),
|
||||||
scrollbar.config(command=canvas.yview)
|
foreground='red',
|
||||||
|
justify='center'
|
||||||
# Frame konten
|
).pack(expand=True)
|
||||||
right = ttk.Frame(canvas)
|
else:
|
||||||
canvas_window = canvas.create_window((0, 0), window=right, anchor='nw')
|
# Panel Kanan: Form Pembayaran (HANYA UNTUK KASIR)
|
||||||
|
right_outer = ttk.LabelFrame(main_container, text="💳 Form Pembayaran", padding=5)
|
||||||
# Update scroll region
|
right_outer.pack(side='right', fill='both', expand=True, padx=(5, 0))
|
||||||
def configure_scroll(event):
|
|
||||||
canvas.configure(scrollregion=canvas.bbox('all'))
|
wrapper = ttk.Frame(right_outer)
|
||||||
|
wrapper.pack(fill='both', expand=True)
|
||||||
right.bind('<Configure>', configure_scroll)
|
|
||||||
|
scrollbar = ttk.Scrollbar(wrapper, orient='vertical')
|
||||||
# Update width
|
scrollbar.pack(side='right', fill='y')
|
||||||
def configure_canvas(event):
|
|
||||||
canvas.itemconfig(canvas_window, width=event.width)
|
canvas = tk.Canvas(wrapper, yscrollcommand=scrollbar.set, highlightthickness=0, height=450)
|
||||||
|
canvas.pack(side='left', fill='both', expand=True)
|
||||||
canvas.bind('<Configure>', configure_canvas)
|
|
||||||
|
scrollbar.config(command=canvas.yview)
|
||||||
# Mouse wheel
|
|
||||||
def on_mousewheel(event):
|
right = ttk.Frame(canvas)
|
||||||
canvas.yview_scroll(-1 * int(event.delta / 120), "units")
|
canvas_window = canvas.create_window((0, 0), window=right, anchor='nw')
|
||||||
|
|
||||||
canvas.bind_all('<MouseWheel>', on_mousewheel)
|
def configure_scroll(event):
|
||||||
|
canvas.configure(scrollregion=canvas.bbox('all'))
|
||||||
# === ISI FORM ===
|
|
||||||
|
right.bind('<Configure>', configure_scroll)
|
||||||
# Info transaksi
|
|
||||||
info_frame = ttk.Frame(right)
|
def configure_canvas(event):
|
||||||
info_frame.pack(fill='x', pady=15, padx=15)
|
canvas.itemconfig(canvas_window, width=event.width)
|
||||||
|
|
||||||
self.selected_transaksi_label = ttk.Label(info_frame, text="Belum ada transaksi dipilih", font=("Arial", 10, "bold"), foreground='red')
|
canvas.bind('<Configure>', configure_canvas)
|
||||||
self.selected_transaksi_label.pack()
|
|
||||||
|
def on_mousewheel(event):
|
||||||
self.selected_total_label = ttk.Label(info_frame, text="Total: Rp 0", font=("Arial", 12, "bold"), foreground='green')
|
canvas.yview_scroll(-1 * int(event.delta / 120), "units")
|
||||||
self.selected_total_label.pack(pady=4)
|
|
||||||
|
canvas.bind_all('<MouseWheel>', on_mousewheel)
|
||||||
ttk.Separator(right, orient='horizontal').pack(fill='x', pady=15, padx=15)
|
|
||||||
|
# Info transaksi
|
||||||
# Metode pembayaran
|
info_frame = ttk.Frame(right)
|
||||||
method_frame = ttk.Frame(right)
|
info_frame.pack(fill='x', pady=15, padx=15)
|
||||||
method_frame.pack(fill='x', pady=10, padx=15)
|
|
||||||
|
self.selected_transaksi_label = ttk.Label(
|
||||||
ttk.Label(method_frame, text="💳 Pilih Metode Pembayaran:", font=("Arial", 10, "bold")).pack(anchor='w', pady=6)
|
info_frame,
|
||||||
|
text="Belum ada transaksi dipilih",
|
||||||
self.payment_method_var = tk.StringVar(value='cash')
|
font=("Arial", 10, "bold"),
|
||||||
|
foreground='red'
|
||||||
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)
|
self.selected_transaksi_label.pack()
|
||||||
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)
|
|
||||||
|
self.selected_total_label = ttk.Label(
|
||||||
ttk.Separator(right, orient='horizontal').pack(fill='x', pady=15, padx=15)
|
info_frame,
|
||||||
|
text="Total: Rp 0",
|
||||||
# Frame input dinamis
|
font=("Arial", 12, "bold"),
|
||||||
self.payment_input_frame = ttk.Frame(right)
|
foreground='green'
|
||||||
self.payment_input_frame.pack(fill='x', pady=10, padx=15)
|
)
|
||||||
|
self.selected_total_label.pack(pady=4)
|
||||||
self.build_cash_input()
|
|
||||||
|
ttk.Separator(right, orient='horizontal').pack(fill='x', pady=15, padx=15)
|
||||||
ttk.Separator(right, orient='horizontal').pack(fill='x', pady=15, padx=15)
|
|
||||||
|
# Metode pembayaran
|
||||||
# Tombol
|
method_frame = ttk.Frame(right)
|
||||||
btn_frame = ttk.Frame(right)
|
method_frame.pack(fill='x', pady=10, padx=15)
|
||||||
btn_frame.pack(fill='x', pady=20, padx=15)
|
|
||||||
|
ttk.Label(method_frame, text="💳 Pilih Metode Pembayaran:", font=("Arial", 10, "bold")).pack(anchor='w', pady=6)
|
||||||
ttk.Button(btn_frame, text="✅ PROSES PEMBAYARAN", command=self.process_payment, style="Accent.TButton").pack(ipadx=30, ipady=10)
|
|
||||||
|
self.payment_method_var = tk.StringVar(value='cash')
|
||||||
# PENTING: Tambah banyak space di bawah biar scrollbar muncul
|
|
||||||
for i in range(20):
|
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.Label(right, text="").pack()
|
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
|
# Load data
|
||||||
self.reload_payment_orders()
|
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):
|
def build_report_tab(self, parent):
|
||||||
"""Tab laporan penjualan untuk admin/pemilik"""
|
"""Tab laporan penjualan untuk admin/pemilik"""
|
||||||
for w in parent.winfo_children():
|
for w in parent.winfo_children():
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user