Revisi+Tambah fitur
This commit is contained in:
parent
911e950085
commit
207f2eeced
630
main.py
630
main.py
@ -825,8 +825,11 @@ class App:
|
|||||||
w.destroy()
|
w.destroy()
|
||||||
top = ttk.Frame(self.root)
|
top = ttk.Frame(self.root)
|
||||||
top.pack(fill='x')
|
top.pack(fill='x')
|
||||||
ttk.Label(top, text=f"User: {self.session['username']} | Role: {self.session['role']}",
|
ttk.Label(
|
||||||
font=("Arial", 12)).pack(side='left', padx=10, pady=6)
|
top,
|
||||||
|
text=f"User: {self.session['username']} | Role: {self.session['role']}",
|
||||||
|
font=("Arial", 12)
|
||||||
|
).pack(side='left', padx=10, pady=6)
|
||||||
ttk.Button(top, text="Logout", command=self.logout).pack(side='right', padx=10)
|
ttk.Button(top, text="Logout", command=self.logout).pack(side='right', padx=10)
|
||||||
main = ttk.Notebook(self.root)
|
main = ttk.Notebook(self.root)
|
||||||
main.pack(fill='both', expand=True, padx=10, pady=8)
|
main.pack(fill='both', expand=True, padx=10, pady=8)
|
||||||
@ -836,49 +839,104 @@ class App:
|
|||||||
self.tab_order = ttk.Frame(main)
|
self.tab_order = ttk.Frame(main)
|
||||||
self.tab_waiter = ttk.Frame(main)
|
self.tab_waiter = ttk.Frame(main)
|
||||||
self.tab_favorite = ttk.Frame(main)
|
self.tab_favorite = ttk.Frame(main)
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# KONDISI TAB BERDASARKAN ROLE
|
||||||
|
# ========================================
|
||||||
|
|
||||||
# Tab untuk semua role
|
# Tab untuk SEMUA role
|
||||||
main.add(self.tab_menu_view, text="Menu - View")
|
main.add(self.tab_menu_view, text="📖 Menu - View")
|
||||||
|
|
||||||
# Tab khusus pembeli (bisa order)
|
# ==========================================
|
||||||
if self.session['role'] in ['pembeli', 'admin', 'user']:
|
# ROLE: PEMBELI / USER
|
||||||
main.add(self.tab_order, text="Order Menu")
|
# ==========================================
|
||||||
|
|
||||||
if self.session['role'] in ['pembeli', 'user']:
|
if self.session['role'] in ['pembeli', 'user']:
|
||||||
main.add(self.tab_favorite, text="Favorit Saya")
|
main.add(self.tab_order, text="🛒 Order Menu")
|
||||||
|
main.add(self.tab_favorite, text="⭐ Favorit Saya")
|
||||||
|
|
||||||
# Tab khusus waiter
|
# ==========================================
|
||||||
if self.session['role'] in ['waiter', 'admin']:
|
# ROLE: WAITER
|
||||||
main.add(self.tab_waiter, text="Waiter - Pesanan")
|
# ==========================================
|
||||||
|
if self.session['role'] == 'waiter':
|
||||||
|
main.add(self.tab_waiter, text="🍽️ Kelola Pesanan")
|
||||||
|
|
||||||
# Tab pembayaran untuk kasir & admin
|
# ==========================================
|
||||||
if self.session['role'] in ['kasir', 'admin']:
|
# ROLE: KASIR (Order + Transaksi SAJA)
|
||||||
|
# ==========================================
|
||||||
|
if self.session['role'] == 'kasir':
|
||||||
|
main.add(self.tab_order, text="🛒 Order Menu")
|
||||||
self.tab_payment = ttk.Frame(main)
|
self.tab_payment = ttk.Frame(main)
|
||||||
main.add(self.tab_payment, text="💰 Pembayaran")
|
main.add(self.tab_payment, text="💰 Transaksi")
|
||||||
|
|
||||||
# Tab khusus admin
|
|
||||||
if self.session['role'] == 'admin':
|
|
||||||
main.add(self.tab_menu_manage, text="Menu - Manage")
|
|
||||||
main.add(self.tab_promo, text="Promo - Manage")
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.build_menu_view_tab(self.tab_menu_view)
|
# ==========================================
|
||||||
|
# ROLE: PEMILIK (Laporan SAJA)
|
||||||
if self.session['role'] in ['pembeli', 'admin', 'user']:
|
# ==========================================
|
||||||
self.build_order_tab(self.tab_order)
|
if self.session['role'] == 'pemilik':
|
||||||
if self.session['role'] in ['pembeli', 'user']:
|
self.tab_report = ttk.Frame(main)
|
||||||
self.build_favorite_tab(self.tab_favorite)
|
main.add(self.tab_report, text="📊 Laporan")
|
||||||
|
|
||||||
if self.session['role'] in ['waiter', 'admin']:
|
# ==========================================
|
||||||
self.build_waiter_tab(self.tab_waiter)
|
# ROLE: ADMIN (Kelola Semua)
|
||||||
|
# ==========================================
|
||||||
if self.session['role'] in ['kasir', 'admin']:
|
|
||||||
self.build_payment_tab(self.tab_payment)
|
|
||||||
|
|
||||||
if self.session['role'] == 'admin':
|
if self.session['role'] == 'admin':
|
||||||
|
# Admin bisa order juga (untuk testing)
|
||||||
|
main.add(self.tab_order, text="🛒 Order Menu")
|
||||||
|
|
||||||
|
# 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")
|
||||||
|
|
||||||
|
# Kelola Menu & Promo (KHUSUS ADMIN)
|
||||||
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# BUILD TAB BERDASARKAN ROLE (REVISI)
|
||||||
|
# ========================================
|
||||||
|
|
||||||
|
# Menu View - untuk SEMUA role
|
||||||
|
self.build_menu_view_tab(self.tab_menu_view)
|
||||||
|
|
||||||
|
# Pembeli/User
|
||||||
|
if self.session['role'] in ['pembeli', 'user']:
|
||||||
|
self.build_order_tab(self.tab_order)
|
||||||
|
self.build_favorite_tab(self.tab_favorite)
|
||||||
|
|
||||||
|
# Waiter
|
||||||
|
if self.session['role'] == 'waiter':
|
||||||
|
self.build_waiter_tab(self.tab_waiter)
|
||||||
|
|
||||||
|
# Kasir (Order + Transaksi SAJA)
|
||||||
|
if self.session['role'] == 'kasir':
|
||||||
|
self.build_order_tab(self.tab_order)
|
||||||
|
self.build_payment_tab(self.tab_payment)
|
||||||
|
|
||||||
|
# Pemilik (Laporan SAJA)
|
||||||
|
if self.session['role'] == 'pemilik':
|
||||||
|
self.build_report_tab(self.tab_report)
|
||||||
|
|
||||||
|
# Admin (Semua Akses)
|
||||||
|
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)
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
def build_menu_view_tab(self, parent):
|
def build_menu_view_tab(self, parent):
|
||||||
for w in parent.winfo_children():
|
for w in parent.winfo_children():
|
||||||
@ -2525,6 +2583,506 @@ class App:
|
|||||||
# Load data
|
# Load data
|
||||||
self.reload_payment_orders()
|
self.reload_payment_orders()
|
||||||
|
|
||||||
|
def build_report_tab(self, parent):
|
||||||
|
"""Tab laporan penjualan untuk admin/pemilik"""
|
||||||
|
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="📊 Laporan Penjualan", font=("Arial", 14, "bold")).pack(side='left')
|
||||||
|
ttk.Button(header, text="🔄 Refresh", command=self.reload_report).pack(side='right', padx=6)
|
||||||
|
|
||||||
|
# Filter frame
|
||||||
|
filter_frame = ttk.LabelFrame(parent, text="🔍 Filter Laporan", padding=10)
|
||||||
|
filter_frame.pack(fill='x', padx=10, pady=6)
|
||||||
|
|
||||||
|
# Row 0: Periode
|
||||||
|
ttk.Label(filter_frame, text="Periode:").grid(row=0, column=0, sticky='w', padx=5, pady=4)
|
||||||
|
self.report_period_var = tk.StringVar(value='harian')
|
||||||
|
|
||||||
|
period_frame = ttk.Frame(filter_frame)
|
||||||
|
period_frame.grid(row=0, column=1, sticky='w', padx=5, pady=4)
|
||||||
|
|
||||||
|
ttk.Radiobutton(period_frame, text="Hari Ini", variable=self.report_period_var, value='harian').pack(side='left', padx=5)
|
||||||
|
ttk.Radiobutton(period_frame, text="Minggu Ini", variable=self.report_period_var, value='mingguan').pack(side='left', padx=5)
|
||||||
|
ttk.Radiobutton(period_frame, text="Bulan Ini", variable=self.report_period_var, value='bulanan').pack(side='left', padx=5)
|
||||||
|
|
||||||
|
# Row 1: Metode Pembayaran
|
||||||
|
ttk.Label(filter_frame, text="Metode:").grid(row=1, column=0, sticky='w', padx=5, pady=4)
|
||||||
|
self.report_method_var = tk.StringVar(value='semua')
|
||||||
|
|
||||||
|
method_combo = ttk.Combobox(filter_frame, textvariable=self.report_method_var, width=20, state='readonly')
|
||||||
|
method_combo['values'] = ('Semua', 'Cash', 'QRIS', 'E-Wallet')
|
||||||
|
method_combo.grid(row=1, column=1, sticky='w', padx=5, pady=4)
|
||||||
|
|
||||||
|
# Tombol generate
|
||||||
|
ttk.Button(
|
||||||
|
filter_frame,
|
||||||
|
text="📊 Generate Laporan",
|
||||||
|
command=self.generate_report,
|
||||||
|
style="Accent.TButton"
|
||||||
|
).grid(row=2, column=0, columnspan=2, pady=10)
|
||||||
|
|
||||||
|
# Summary frame
|
||||||
|
summary_frame = ttk.LabelFrame(parent, text="📈 Ringkasan", padding=10)
|
||||||
|
summary_frame.pack(fill='x', padx=10, pady=6)
|
||||||
|
|
||||||
|
summary_inner = ttk.Frame(summary_frame)
|
||||||
|
summary_inner.pack()
|
||||||
|
|
||||||
|
# Labels untuk ringkasan
|
||||||
|
ttk.Label(summary_inner, text="Total Transaksi:").grid(row=0, column=0, sticky='w', padx=10, pady=3)
|
||||||
|
self.report_total_trx_label = ttk.Label(summary_inner, text="0", font=("Arial", 10, "bold"))
|
||||||
|
self.report_total_trx_label.grid(row=0, column=1, sticky='w', padx=10, pady=3)
|
||||||
|
|
||||||
|
ttk.Label(summary_inner, text="Total Pendapatan:").grid(row=1, column=0, sticky='w', padx=10, pady=3)
|
||||||
|
self.report_total_income_label = ttk.Label(
|
||||||
|
summary_inner,
|
||||||
|
text="Rp 0",
|
||||||
|
font=("Arial", 10, "bold"),
|
||||||
|
foreground='green'
|
||||||
|
)
|
||||||
|
self.report_total_income_label.grid(row=1, column=1, sticky='w', padx=10, pady=3)
|
||||||
|
|
||||||
|
ttk.Label(summary_inner, text="Rata-rata/Transaksi:").grid(row=2, column=0, sticky='w', padx=10, pady=3)
|
||||||
|
self.report_avg_label = ttk.Label(summary_inner, text="Rp 0", font=("Arial", 10, "bold"))
|
||||||
|
self.report_avg_label.grid(row=2, column=1, sticky='w', padx=10, pady=3)
|
||||||
|
|
||||||
|
# Detail tabel
|
||||||
|
detail_frame = ttk.LabelFrame(parent, text="📋 Detail Transaksi", padding=10)
|
||||||
|
detail_frame.pack(fill='both', expand=True, padx=10, pady=6)
|
||||||
|
|
||||||
|
# Treeview
|
||||||
|
tree_frame = ttk.Frame(detail_frame)
|
||||||
|
tree_frame.pack(fill='both', expand=True)
|
||||||
|
|
||||||
|
tree_scroll = ttk.Scrollbar(tree_frame, orient='vertical')
|
||||||
|
tree_scroll.pack(side='right', fill='y')
|
||||||
|
|
||||||
|
cols = ("ID", "Tanggal", "Meja", "Total", "Metode", "Status")
|
||||||
|
self.report_tree = ttk.Treeview(
|
||||||
|
tree_frame,
|
||||||
|
columns=cols,
|
||||||
|
show='headings',
|
||||||
|
height=10,
|
||||||
|
yscrollcommand=tree_scroll.set
|
||||||
|
)
|
||||||
|
|
||||||
|
tree_scroll.config(command=self.report_tree.yview)
|
||||||
|
|
||||||
|
self.report_tree.heading("ID", text="ID")
|
||||||
|
self.report_tree.heading("Tanggal", text="Tanggal")
|
||||||
|
self.report_tree.heading("Meja", text="Meja")
|
||||||
|
self.report_tree.heading("Total", text="Total")
|
||||||
|
self.report_tree.heading("Metode", text="Metode")
|
||||||
|
self.report_tree.heading("Status", text="Status")
|
||||||
|
|
||||||
|
self.report_tree.column("ID", width=50)
|
||||||
|
self.report_tree.column("Tanggal", width=140)
|
||||||
|
self.report_tree.column("Meja", width=60)
|
||||||
|
self.report_tree.column("Total", width=100)
|
||||||
|
self.report_tree.column("Metode", width=120)
|
||||||
|
self.report_tree.column("Status", width=80)
|
||||||
|
|
||||||
|
self.report_tree.pack(side='left', fill='both', expand=True)
|
||||||
|
|
||||||
|
# Tombol grafik
|
||||||
|
btn_frame = ttk.Frame(parent)
|
||||||
|
btn_frame.pack(pady=10)
|
||||||
|
ttk.Button(btn_frame, text="📊 Tampilkan Grafik", command=self.show_report_chart).pack()
|
||||||
|
|
||||||
|
def build_user_manage_tab(self, parent):
|
||||||
|
"""Tab kelola user (admin bisa tambah/edit kasir)"""
|
||||||
|
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="👥 Kelola User & Kasir", font=("Arial", 14, "bold")).pack(side='left')
|
||||||
|
ttk.Button(header, text="➕ Tambah User", command=self.open_add_user_window).pack(side='right', padx=6)
|
||||||
|
|
||||||
|
# Treeview user
|
||||||
|
tree_frame = ttk.Frame(parent)
|
||||||
|
tree_frame.pack(fill='both', expand=True, padx=10, pady=6)
|
||||||
|
|
||||||
|
tree_scroll = ttk.Scrollbar(tree_frame, orient='vertical')
|
||||||
|
tree_scroll.pack(side='right', fill='y')
|
||||||
|
|
||||||
|
cols = ("ID", "Username", "Role")
|
||||||
|
self.user_tree = ttk.Treeview(tree_frame, columns=cols, show='headings', height=15, yscrollcommand=tree_scroll.set)
|
||||||
|
|
||||||
|
tree_scroll.config(command=self.user_tree.yview)
|
||||||
|
|
||||||
|
self.user_tree.heading("ID", text="ID")
|
||||||
|
self.user_tree.heading("Username", text="Username")
|
||||||
|
self.user_tree.heading("Role", text="Role")
|
||||||
|
|
||||||
|
self.user_tree.column("ID", width=80)
|
||||||
|
self.user_tree.column("Username", width=200)
|
||||||
|
self.user_tree.column("Role", width=150)
|
||||||
|
|
||||||
|
self.user_tree.pack(side='left', fill='both', expand=True)
|
||||||
|
|
||||||
|
# Tombol aksi
|
||||||
|
btn_frame = ttk.Frame(parent)
|
||||||
|
btn_frame.pack(pady=10)
|
||||||
|
|
||||||
|
ttk.Button(btn_frame, text="✏️ Edit User", command=self.open_edit_user_window).pack(side='left', padx=5)
|
||||||
|
ttk.Button(btn_frame, text="🗑️ Hapus User", command=self.delete_selected_user).pack(side='left', padx=5)
|
||||||
|
ttk.Button(btn_frame, text="🔄 Refresh", command=self.reload_user_table).pack(side='left', padx=5)
|
||||||
|
|
||||||
|
# Load data
|
||||||
|
self.reload_user_table()
|
||||||
|
|
||||||
|
def reload_user_table(self):
|
||||||
|
"""Reload tabel user"""
|
||||||
|
# Clear tree
|
||||||
|
for r in self.user_tree.get_children():
|
||||||
|
self.user_tree.delete(r)
|
||||||
|
|
||||||
|
# Get users
|
||||||
|
users = read_all(USERS_CSV)
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
uid = user.get('id')
|
||||||
|
username = user.get('username')
|
||||||
|
role = user.get('role')
|
||||||
|
|
||||||
|
self.user_tree.insert("", tk.END, values=(uid, username, role))
|
||||||
|
|
||||||
|
def open_add_user_window(self):
|
||||||
|
"""Popup untuk tambah user baru"""
|
||||||
|
w = tk.Toplevel(self.root)
|
||||||
|
w.title("➕ Tambah User Baru")
|
||||||
|
w.geometry("400x280")
|
||||||
|
w.transient(self.root)
|
||||||
|
w.grab_set()
|
||||||
|
|
||||||
|
frm = ttk.Frame(w, padding=15)
|
||||||
|
frm.pack(fill='both', expand=True)
|
||||||
|
|
||||||
|
ttk.Label(frm, text="Tambah User Baru", font=("Arial", 12, "bold")).pack(pady=10)
|
||||||
|
|
||||||
|
# Username
|
||||||
|
ttk.Label(frm, text="Username:").pack(anchor='w', pady=(10, 2))
|
||||||
|
username_var = tk.StringVar()
|
||||||
|
ttk.Entry(frm, textvariable=username_var, width=30).pack(fill='x', pady=(0, 10))
|
||||||
|
|
||||||
|
# Password
|
||||||
|
ttk.Label(frm, text="Password:").pack(anchor='w', pady=2)
|
||||||
|
password_var = tk.StringVar()
|
||||||
|
ttk.Entry(frm, textvariable=password_var, show="*", width=30).pack(fill='x', pady=(0, 10))
|
||||||
|
|
||||||
|
# Role
|
||||||
|
ttk.Label(frm, text="Role:").pack(anchor='w', pady=2)
|
||||||
|
role_var = tk.StringVar(value='kasir')
|
||||||
|
|
||||||
|
role_frame = ttk.Frame(frm)
|
||||||
|
role_frame.pack(fill='x', pady=(0, 10))
|
||||||
|
|
||||||
|
ttk.Radiobutton(role_frame, text="Kasir", variable=role_var, value='kasir').pack(side='left', padx=5)
|
||||||
|
ttk.Radiobutton(role_frame, text="Waiter", variable=role_var, value='waiter').pack(side='left', padx=5)
|
||||||
|
ttk.Radiobutton(role_frame, text="Pembeli", variable=role_var, value='pembeli').pack(side='left', padx=5)
|
||||||
|
|
||||||
|
def save_user():
|
||||||
|
username = username_var.get().strip()
|
||||||
|
password = password_var.get().strip()
|
||||||
|
role = role_var.get()
|
||||||
|
|
||||||
|
if not username or not password:
|
||||||
|
messagebox.showerror("Error", "Username dan password harus diisi!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Cek username sudah ada
|
||||||
|
users = read_all(USERS_CSV)
|
||||||
|
for u in users:
|
||||||
|
if u.get('username') == username:
|
||||||
|
messagebox.showerror("Error", f"Username '{username}' sudah digunakan!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Tambah user
|
||||||
|
new_id = next_int_id(users, 'id')
|
||||||
|
users.append({
|
||||||
|
'id': new_id,
|
||||||
|
'username': username,
|
||||||
|
'password': password,
|
||||||
|
'role': role
|
||||||
|
})
|
||||||
|
|
||||||
|
write_all(USERS_CSV, ["id", "username", "password", "role"], users)
|
||||||
|
|
||||||
|
messagebox.showinfo("Sukses", f"User '{username}' berhasil ditambahkan!")
|
||||||
|
w.destroy()
|
||||||
|
self.reload_user_table()
|
||||||
|
|
||||||
|
ttk.Button(frm, text="💾 Simpan", command=save_user, style="Accent.TButton").pack(pady=15)
|
||||||
|
|
||||||
|
def open_edit_user_window(self):
|
||||||
|
"""Popup untuk edit user"""
|
||||||
|
sel = self.user_tree.selection()
|
||||||
|
if not sel:
|
||||||
|
messagebox.showwarning("Pilih User", "Pilih user yang akan diedit")
|
||||||
|
return
|
||||||
|
|
||||||
|
item = self.user_tree.item(sel)['values']
|
||||||
|
user_id = item[0]
|
||||||
|
|
||||||
|
# Get user data
|
||||||
|
users = read_all(USERS_CSV)
|
||||||
|
user_data = None
|
||||||
|
for u in users:
|
||||||
|
if u.get('id') == str(user_id):
|
||||||
|
user_data = u
|
||||||
|
break
|
||||||
|
|
||||||
|
if not user_data:
|
||||||
|
messagebox.showerror("Error", "User tidak ditemukan")
|
||||||
|
return
|
||||||
|
|
||||||
|
w = tk.Toplevel(self.root)
|
||||||
|
w.title("✏️ Edit User")
|
||||||
|
w.geometry("400x280")
|
||||||
|
w.transient(self.root)
|
||||||
|
w.grab_set()
|
||||||
|
|
||||||
|
frm = ttk.Frame(w, padding=15)
|
||||||
|
frm.pack(fill='both', expand=True)
|
||||||
|
|
||||||
|
ttk.Label(frm, text=f"Edit User: {user_data.get('username')}", font=("Arial", 12, "bold")).pack(pady=10)
|
||||||
|
|
||||||
|
# Password baru
|
||||||
|
ttk.Label(frm, text="Password Baru (kosongkan jika tidak diubah):").pack(anchor='w', pady=2)
|
||||||
|
password_var = tk.StringVar()
|
||||||
|
ttk.Entry(frm, textvariable=password_var, show="*", width=30).pack(fill='x', pady=(0, 10))
|
||||||
|
|
||||||
|
# Role
|
||||||
|
ttk.Label(frm, text="Role:").pack(anchor='w', pady=2)
|
||||||
|
role_var = tk.StringVar(value=user_data.get('role'))
|
||||||
|
|
||||||
|
role_frame = ttk.Frame(frm)
|
||||||
|
role_frame.pack(fill='x', pady=(0, 10))
|
||||||
|
|
||||||
|
ttk.Radiobutton(role_frame, text="Kasir", variable=role_var, value='kasir').pack(side='left', padx=5)
|
||||||
|
ttk.Radiobutton(role_frame, text="Waiter", variable=role_var, value='waiter').pack(side='left', padx=5)
|
||||||
|
ttk.Radiobutton(role_frame, text="Pembeli", variable=role_var, value='pembeli').pack(side='left', padx=5)
|
||||||
|
ttk.Radiobutton(role_frame, text="Admin", variable=role_var, value='admin').pack(side='left', padx=5)
|
||||||
|
|
||||||
|
def update_user():
|
||||||
|
new_password = password_var.get().strip()
|
||||||
|
new_role = role_var.get()
|
||||||
|
|
||||||
|
# Update user
|
||||||
|
for u in users:
|
||||||
|
if u.get('id') == str(user_id):
|
||||||
|
if new_password:
|
||||||
|
u['password'] = new_password
|
||||||
|
u['role'] = new_role
|
||||||
|
break
|
||||||
|
|
||||||
|
write_all(USERS_CSV, ["id", "username", "password", "role"], users)
|
||||||
|
|
||||||
|
messagebox.showinfo("Sukses", "User berhasil diupdate!")
|
||||||
|
w.destroy()
|
||||||
|
self.reload_user_table()
|
||||||
|
|
||||||
|
ttk.Button(frm, text="💾 Update", command=update_user, style="Accent.TButton").pack(pady=15)
|
||||||
|
|
||||||
|
def delete_selected_user(self):
|
||||||
|
"""Hapus user yang dipilih"""
|
||||||
|
sel = self.user_tree.selection()
|
||||||
|
if not sel:
|
||||||
|
messagebox.showwarning("Pilih User", "Pilih user yang akan dihapus")
|
||||||
|
return
|
||||||
|
|
||||||
|
item = self.user_tree.item(sel)['values']
|
||||||
|
user_id = item[0]
|
||||||
|
username = item[1]
|
||||||
|
|
||||||
|
# Cek jangan hapus admin
|
||||||
|
if username == 'admin':
|
||||||
|
messagebox.showerror("Error", "Tidak bisa menghapus user admin!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Konfirmasi
|
||||||
|
if not messagebox.askyesno("Konfirmasi", f"Hapus user '{username}'?"):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Hapus
|
||||||
|
users = read_all(USERS_CSV)
|
||||||
|
users = [u for u in users if u.get('id') != str(user_id)]
|
||||||
|
|
||||||
|
write_all(USERS_CSV, ["id", "username", "password", "role"], users)
|
||||||
|
|
||||||
|
messagebox.showinfo("Sukses", f"User '{username}' berhasil dihapus!")
|
||||||
|
self.reload_user_table()
|
||||||
|
|
||||||
|
|
||||||
|
def reload_report(self):
|
||||||
|
"""Reload data laporan"""
|
||||||
|
self.generate_report()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_report(self):
|
||||||
|
"""Generate laporan berdasarkan filter"""
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
# Clear tree
|
||||||
|
for r in self.report_tree.get_children():
|
||||||
|
self.report_tree.delete(r)
|
||||||
|
|
||||||
|
# Get filter
|
||||||
|
period = self.report_period_var.get()
|
||||||
|
method_filter = self.report_method_var.get().lower()
|
||||||
|
|
||||||
|
# Hitung tanggal range
|
||||||
|
today = datetime.now()
|
||||||
|
|
||||||
|
if period == 'harian':
|
||||||
|
start_date = today.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
elif period == 'mingguan':
|
||||||
|
start_date = today - timedelta(days=today.weekday())
|
||||||
|
start_date = start_date.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
else: # bulanan
|
||||||
|
start_date = today.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
|
# Get data transaksi yang sudah dibayar
|
||||||
|
all_transaksi = transaksi_list(status='dibayar')
|
||||||
|
|
||||||
|
filtered_transaksi = []
|
||||||
|
total_income = 0
|
||||||
|
|
||||||
|
for trx in all_transaksi:
|
||||||
|
tid, uid, meja, total, status, promo_code, tanggal = trx
|
||||||
|
|
||||||
|
# Parse tanggal
|
||||||
|
try:
|
||||||
|
trx_date = datetime.strptime(tanggal, "%Y-%m-%d %H:%M:%S")
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Filter by date range
|
||||||
|
if trx_date < start_date:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get payment info
|
||||||
|
payment_data = pembayaran_get_by_transaksi(tid)
|
||||||
|
if not payment_data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
pid, metode, jumlah, status_bayar, tanggal_bayar, struk = payment_data
|
||||||
|
|
||||||
|
# Filter by method
|
||||||
|
if method_filter != 'semua':
|
||||||
|
if method_filter == 'cash' and metode != 'cash':
|
||||||
|
continue
|
||||||
|
elif method_filter == 'qris' and metode != 'qris':
|
||||||
|
continue
|
||||||
|
elif method_filter == 'e-wallet' and not metode.startswith('ewallet'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Add to filtered
|
||||||
|
filtered_transaksi.append((tid, tanggal, meja, total, metode, status))
|
||||||
|
total_income += total
|
||||||
|
|
||||||
|
# Insert to tree
|
||||||
|
self.report_tree.insert(
|
||||||
|
"",
|
||||||
|
tk.END,
|
||||||
|
values=(tid, tanggal, meja, f"Rp {total:,.0f}", metode.upper(), status)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update summary
|
||||||
|
count = len(filtered_transaksi)
|
||||||
|
avg = total_income / count if count > 0 else 0
|
||||||
|
|
||||||
|
self.report_total_trx_label.config(text=str(count))
|
||||||
|
self.report_total_income_label.config(text=f"Rp {total_income:,.0f}")
|
||||||
|
self.report_avg_label.config(text=f"Rp {avg:,.0f}")
|
||||||
|
|
||||||
|
|
||||||
|
def show_report_chart(self):
|
||||||
|
"""Tampilkan grafik laporan menggunakan matplotlib"""
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Get data from tree
|
||||||
|
if not self.report_tree.get_children():
|
||||||
|
messagebox.showwarning("Tidak Ada Data", "Generate laporan terlebih dahulu")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Collect data
|
||||||
|
metode_counts = {}
|
||||||
|
total_per_day = {}
|
||||||
|
|
||||||
|
for item_id in self.report_tree.get_children():
|
||||||
|
values = self.report_tree.item(item_id)['values']
|
||||||
|
tid, tanggal, meja, total_str, metode, status = values
|
||||||
|
|
||||||
|
# Parse total
|
||||||
|
total = float(total_str.replace('Rp ', '').replace(',', '').replace('.', ''))
|
||||||
|
|
||||||
|
# Count by metode
|
||||||
|
metode_counts[metode] = metode_counts.get(metode, 0) + 1
|
||||||
|
|
||||||
|
# Sum by date
|
||||||
|
try:
|
||||||
|
date_obj = datetime.strptime(tanggal, "%Y-%m-%d %H:%M:%S")
|
||||||
|
date_key = date_obj.strftime("%Y-%m-%d")
|
||||||
|
total_per_day[date_key] = total_per_day.get(date_key, 0) + total
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Create window
|
||||||
|
chart_window = tk.Toplevel(self.root)
|
||||||
|
chart_window.title("📊 Grafik Laporan Penjualan")
|
||||||
|
chart_window.geometry("900x600")
|
||||||
|
|
||||||
|
# Create figure with 2 subplots
|
||||||
|
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
|
||||||
|
|
||||||
|
# Chart 1: Pie chart metode pembayaran
|
||||||
|
if metode_counts:
|
||||||
|
labels = list(metode_counts.keys())
|
||||||
|
sizes = list(metode_counts.values())
|
||||||
|
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8']
|
||||||
|
|
||||||
|
ax1.pie(sizes, labels=labels, autopct='%1.1f%%', colors=colors, startangle=90)
|
||||||
|
ax1.set_title('Transaksi per Metode Pembayaran', fontsize=12, fontweight='bold')
|
||||||
|
|
||||||
|
# Chart 2: Bar chart pendapatan per hari
|
||||||
|
if total_per_day:
|
||||||
|
dates = sorted(total_per_day.keys())
|
||||||
|
totals = [total_per_day[d] for d in dates]
|
||||||
|
|
||||||
|
date_labels = [
|
||||||
|
datetime.strptime(d, "%Y-%m-%d").strftime("%d/%m")
|
||||||
|
for d in dates
|
||||||
|
]
|
||||||
|
|
||||||
|
ax2.bar(date_labels, totals, color='#4ECDC4')
|
||||||
|
ax2.set_xlabel('Tanggal', fontweight='bold')
|
||||||
|
ax2.set_ylabel('Pendapatan (Rp)', fontweight='bold')
|
||||||
|
ax2.set_title('Pendapatan Harian', fontsize=12, fontweight='bold')
|
||||||
|
ax2.tick_params(axis='x', rotation=45)
|
||||||
|
|
||||||
|
ax2.yaxis.set_major_formatter(
|
||||||
|
plt.FuncFormatter(lambda x, p: f'Rp {x/1000:.0f}K')
|
||||||
|
)
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
|
||||||
|
# Embed in Tkinter
|
||||||
|
canvas = FigureCanvasTkAgg(fig, master=chart_window)
|
||||||
|
canvas.draw()
|
||||||
|
canvas.get_tk_widget().pack(fill='both', expand=True)
|
||||||
|
|
||||||
|
ttk.Button(chart_window, text="Tutup", command=chart_window.destroy).pack(pady=10)
|
||||||
|
|
||||||
|
|
||||||
def reload_payment_orders(self):
|
def reload_payment_orders(self):
|
||||||
"""Load transaksi dengan status 'selesai' yang belum dibayar"""
|
"""Load transaksi dengan status 'selesai' yang belum dibayar"""
|
||||||
# Clear tree
|
# Clear tree
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user