2025-12-06 15:17:33 +07:00

1019 lines
34 KiB
Python

"""akun default buat login :
- admin / admin123 (role admin)
- kasir / kasir123 (role kasir)
- waiter / waiter123 (role waiter)
- user / user123 (role pembeli)
- owner / owner123 (role pemilik)
"""
import os
import csv
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from PIL import Image, ImageTk
from datetime import datetime
USERS_CSV = "users.csv"
MENU_CSV = "menu.csv"
PROMO_CSV = "promo.csv"
TRANSAKSI_CSV = "transaksi.csv"
DETAIL_TRANSAKSI_CSV = "detail_transaksi.csv"
FAVORITES_CSV = "favorites.csv"
IMG_PREVIEW_SIZE = (120, 80)
def ensure_file(path, fieldnames):
if not os.path.exists(path):
with open(path, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
def read_all(path):
if not os.path.exists(path):
return []
with open(path, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
return list(reader)
def write_all(path, fieldnames, rows):
with open(path, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)
def next_int_id(rows, id_field="id"):
max_id = 0
for r in rows:
try:
v = int(r.get(id_field, 0) or 0)
if v > max_id:
max_id = v
except:
continue
return str(max_id + 1)
def init_db_csv():
ensure_file(USERS_CSV, ["id", "username", "password", "role"])
ensure_file(MENU_CSV, ["id", "nama", "kategori", "harga", "stok", "foto", "tersedia", "item_discount_pct"])
ensure_file(PROMO_CSV, ["code", "type", "value", "min_total"])
ensure_file(TRANSAKSI_CSV, ["id", "user_id", "tanggal", "nomor_meja", "total", "status", "metode_pembayaran"])
ensure_file(DETAIL_TRANSAKSI_CSV, ["id", "transaksi_id", "menu_id", "nama_menu", "jumlah", "harga_satuan", "subtotal"])
ensure_file(FAVORITES_CSV, ["user_id", "menu_id", "count"])
seed_defaults()
# buat masukin data/sample ke database csv
def seed_defaults():
users = read_all(USERS_CSV)
if not users:
defaults = [
('admin','admin123','admin'),
('kasir','kasir123','kasir'),
('waiter','waiter123','waiter'),
('user','user123','pembeli'),
('owner','owner123','pemilik'),
]
rows = []
for i,(u,p,r) in enumerate(defaults, start=1):
rows.append({"id": str(i), "username": u, "password": p, "role": r})
write_all(USERS_CSV, ["id","username","password","role"], rows)
menu_rows = read_all(MENU_CSV)
if not menu_rows:
sample = [
('Americano','Minuman',20000,10,None,1,0),
('Latte','Minuman',25000,5,None,1,10),
('Banana Cake','Dessert',30000,2,None,1,0),
('Nasi Goreng','Makanan',35000,0,None,0,0),
]
rows = []
for i,(name,kategori,harga,stok,foto,tersedia,disc) in enumerate(sample, start=1):
rows.append({
"id": str(i),
"nama": name,
"kategori": kategori,
"harga": str(harga),
"stok": str(stok),
"foto": foto or "",
"tersedia": str(tersedia),
"item_discount_pct": str(disc)
})
write_all(MENU_CSV, ["id","nama","kategori","harga","stok","foto","tersedia","item_discount_pct"], rows)
promo_rows = read_all(PROMO_CSV)
if not promo_rows:
promos = [
('PARDEDE','percent',10,0),
('BOTAK','fixed',5000,20000),
]
rows = []
for code,ptype,val,min_total in promos:
rows.append({
"code": code,
"type": ptype,
"value": str(val),
"min_total": str(min_total)
})
write_all(PROMO_CSV, ["code","type","value","min_total"], rows)
def authenticate(username, password):
rows = read_all(USERS_CSV)
for r in rows:
if r.get("username") == username and r.get("password") == password:
return {'id': int(r.get("id")), 'username': r.get("username"), 'role': r.get("role")}
return None
# Wilayah dikuasai Menu
def menu_add(nama, kategori, harga, stok, foto, item_discount_pct=0):
rows = read_all(MENU_CSV)
new_id = next_int_id(rows, "id")
tersedia = "1" if int(stok) > 0 else "0"
rows.append({
"id": new_id,
"nama": nama,
"kategori": kategori,
"harga": str(float(harga)),
"stok": str(int(stok)),
"foto": foto or "",
"tersedia": tersedia,
"item_discount_pct": str(float(item_discount_pct))
})
write_all(MENU_CSV, ["id","nama","kategori","harga","stok","foto","tersedia","item_discount_pct"], rows)
def menu_update(menu_id, nama, kategori, harga, stok, foto, item_discount_pct=0):
rows = read_all(MENU_CSV)
found = False
for r in rows:
if r.get("id") == str(menu_id):
r["nama"] = nama
r["kategori"] = kategori
r["harga"] = str(float(harga))
r["stok"] = str(int(stok))
r["foto"] = foto or ""
r["tersedia"] = "1" if int(stok) > 0 else "0"
r["item_discount_pct"] = str(float(item_discount_pct))
found = True
break
if found:
write_all(MENU_CSV, ["id","nama","kategori","harga","stok","foto","tersedia","item_discount_pct"], rows)
else:
raise ValueError("Menu id tidak ditemukan")
def menu_delete(menu_id):
rows = read_all(MENU_CSV)
newrows = [r for r in rows if r.get("id") != str(menu_id)]
write_all(MENU_CSV, ["id","nama","kategori","harga","stok","foto","tersedia","item_discount_pct"], newrows)
def menu_list(kategori=None, available_only=False, search_text=None):
rows = read_all(MENU_CSV)
out = []
for r in rows:
if kategori and r.get("kategori") != kategori:
continue
if available_only and r.get("tersedia") != "1":
continue
if search_text:
s = search_text.lower()
if s not in (r.get("nama","").lower() or "") and s not in (r.get("kategori","").lower() or ""):
continue
try:
mid = int(r.get("id") or 0)
except:
mid = r.get("id")
try:
harga = float(r.get("harga") or 0.0)
except:
harga = 0.0
try:
stok = int(float(r.get("stok") or 0))
except:
stok = 0
foto = r.get("foto") or None
tersedia = 1 if r.get("tersedia") == "1" else 0
try:
item_disc = float(r.get("item_discount_pct") or 0.0)
except:
item_disc = 0.0
out.append((mid, r.get("nama"), r.get("kategori"), harga, stok, foto, tersedia, item_disc))
out.sort(key=lambda x: int(x[0]))
return out
def menu_get(menu_id):
rows = read_all(MENU_CSV)
for r in rows:
if r.get("id") == str(menu_id):
try:
mid = int(r.get("id") or 0)
except:
mid = r.get("id")
try:
harga = float(r.get("harga") or 0.0)
except:
harga = 0.0
try:
stok = int(float(r.get("stok") or 0))
except:
stok = 0
foto = r.get("foto") or None
tersedia = 1 if r.get("tersedia") == "1" else 0
try:
item_disc = float(r.get("item_discount_pct") or 0.0)
except:
item_disc = 0.0
return (mid, r.get("nama"), r.get("kategori"), harga, stok, foto, tersedia, item_disc)
return None
def menu_decrease_stock(menu_id, qty):
rows = read_all(MENU_CSV)
found = False
for r in rows:
if r.get("id") == str(menu_id):
found = True
try:
stok = int(float(r.get("stok") or 0))
except:
stok = 0
if stok < qty:
return False, "Stok tidak cukup"
newstok = stok - qty
r["stok"] = str(newstok)
r["tersedia"] = "1" if newstok > 0 else "0"
break
if not found:
return False, "Menu tidak ditemukan"
write_all(MENU_CSV, ["id","nama","kategori","harga","stok","foto","tersedia","item_discount_pct"], rows)
return True, newstok
# Reza balap liar
# wilayah dikuasai promo
def promo_add(code, ptype, value, min_total=0):
rows = read_all(PROMO_CSV)
for r in rows:
if r.get("code") == code:
raise ValueError("Kode promo sudah ada")
rows.append({
"code": code,
"type": ptype,
"value": str(float(value)),
"min_total": str(float(min_total))
})
write_all(PROMO_CSV, ["code","type","value","min_total"], rows)
def promo_update(code, ptype, value, min_total=0):
rows = read_all(PROMO_CSV)
found = False
for r in rows:
if r.get("code") == code:
r["type"] = ptype
r["value"] = str(float(value))
r["min_total"] = str(float(min_total))
found = True
break
if not found:
raise ValueError("Promo tidak ditemukan")
write_all(PROMO_CSV, ["code","type","value","min_total"], rows)
def promo_delete(code):
rows = read_all(PROMO_CSV)
newrows = [r for r in rows if r.get("code") != code]
write_all(PROMO_CSV, ["code","type","value","min_total"], newrows)
def promo_list():
rows = read_all(PROMO_CSV)
out = []
for r in rows:
try:
val = float(r.get("value") or 0.0)
except:
val = 0.0
try:
mt = float(r.get("min_total") or 0.0)
except:
mt = 0.0
out.append((r.get("code"), r.get("type"), val, mt))
out.sort(key=lambda x: x[0] or "")
return out
def promo_get(code):
rows = read_all(PROMO_CSV)
for r in rows:
if r.get("code") == code:
try:
val = float(r.get("value") or 0.0)
except:
val = 0.0
try:
mt = float(r.get("min_total") or 0.0)
except:
mt = 0.0
return (r.get("code"), r.get("type"), val, mt)
return None
# 19 juta lapangan badmin
# Buat logika diskon + promok
def apply_discounts_and_promo(cart_items, promo_code=None):
subtotal = 0.0
item_discount_total = 0.0
menu_rows = read_all(MENU_CSV)
menu_dict = {r["id"]: r for r in menu_rows}
for it in cart_items:
mid = str(it.get('menu_id'))
r = menu_dict.get(mid)
if not r:
continue
try:
price = float(r.get("harga") or 0.0)
except:
price = 0.0
try:
item_disc_pct = float(r.get("item_discount_pct") or 0.0)
except:
item_disc_pct = 0.0
qty = int(it.get('qty', 1))
line = price * qty
subtotal += line
if item_disc_pct and item_disc_pct > 0:
item_discount_total += (price * qty) * (item_disc_pct / 100.0)
promo_discount = 0.0
promo_applied = None
if promo_code:
p = promo_get(promo_code)
if p:
_, ptype, val, min_total = p
if subtotal >= (min_total or 0.0):
if ptype == 'percent':
promo_discount = (subtotal - item_discount_total) * (val / 100.0)
else:
promo_discount = val
promo_applied = promo_code
total = subtotal - item_discount_total - promo_discount
if total < 0:
total = 0.0
return {
'subtotal': round(subtotal, 2),
'item_discount': round(item_discount_total, 2),
'promo_code': promo_applied,
'promo_discount': round(promo_discount, 2),
'total': round(total, 2)
}
# TRANSAKSI FUNCTIONS (2nd Person : Jevvvv)
from datetime import datetime
def create_transaksi(user_id, nomor_meja, cart_items):
"""Membuat transaksi baru dengan status 'Menunggu'"""
rows = read_all(TRANSAKSI_CSV)
trans_id = next_int_id(rows, "id")
# Hitung total
total = 0.0
for item in cart_items:
total += item['subtotal']
trans = {
"id": trans_id,
"user_id": str(user_id),
"tanggal": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"nomor_meja": str(nomor_meja),
"total": str(total),
"status": "Menunggu", # Status: Menunggu, Diproses, Selesai
"metode_pembayaran": ""
}
rows.append(trans)
write_all(TRANSAKSI_CSV, ["id", "user_id", "tanggal", "nomor_meja", "total", "status", "metode_pembayaran"], rows)
# Simpan detail transaksi
detail_rows = read_all(DETAIL_TRANSAKSI_CSV)
for item in cart_items:
detail_id = next_int_id(detail_rows, "id")
detail = {
"id": detail_id,
"transaksi_id": trans_id,
"menu_id": str(item['menu_id']),
"nama_menu": item['nama'],
"jumlah": str(item['qty']),
"harga_satuan": str(item['harga']),
"subtotal": str(item['subtotal'])
}
detail_rows.append(detail)
write_all(DETAIL_TRANSAKSI_CSV, ["id", "transaksi_id", "menu_id", "nama_menu", "jumlah", "harga_satuan", "subtotal"], detail_rows)
# Kurangi stok
for item in cart_items:
menu_decrease_stock(item['menu_id'], item['qty'])
return trans_id
def get_all_transaksi(status=None):
"""Ambil semua transaksi, bisa filter by status"""
rows = read_all(TRANSAKSI_CSV)
result = []
for r in rows:
if status and r.get("status") != status:
continue
result.append({
'id': r.get('id'),
'user_id': r.get('user_id'),
'tanggal': r.get('tanggal'),
'nomor_meja': r.get('nomor_meja'),
'total': float(r.get('total', 0)),
'status': r.get('status'),
'metode_pembayaran': r.get('metode_pembayaran', '')
})
return result
def get_transaksi_detail(transaksi_id):
"""Ambil detail item dari transaksi"""
rows = read_all(DETAIL_TRANSAKSI_CSV)
result = []
for r in rows:
if r.get("transaksi_id") == str(transaksi_id):
result.append({
'id': r.get('id'),
'menu_id': r.get('menu_id'),
'nama_menu': r.get('nama_menu'),
'jumlah': int(r.get('jumlah', 0)),
'harga_satuan': float(r.get('harga_satuan', 0)),
'subtotal': float(r.get('subtotal', 0))
})
return result
def update_transaksi_status(transaksi_id, status):
"""Update status transaksi (Menunggu -> Diproses -> Selesai)"""
rows = read_all(TRANSAKSI_CSV)
for r in rows:
if r.get("id") == str(transaksi_id):
r["status"] = status
break
write_all(TRANSAKSI_CSV, ["id", "user_id", "tanggal", "nomor_meja", "total", "status", "metode_pembayaran"], rows)
# FAVORITES FUNCTIONS (2nd Person: JEVVVV)
def add_to_favorites(user_id, menu_id):
"""Tambah atau update counter menu favorit user"""
rows = read_all(FAVORITES_CSV)
found = False
for r in rows:
if r.get("user_id") == str(user_id) and r.get("menu_id") == str(menu_id):
r["count"] = str(int(r.get("count", 0)) + 1)
found = True
break
if not found:
rows.append({
"user_id": str(user_id),
"menu_id": str(menu_id),
"count": "1"
})
write_all(FAVORITES_CSV, ["user_id", "menu_id", "count"], rows)
def get_user_favorites(user_id, limit=5):
"""Ambil menu favorit user (paling sering dipesan)"""
rows = read_all(FAVORITES_CSV)
user_favs = []
for r in rows:
if r.get("user_id") == str(user_id):
user_favs.append({
'menu_id': int(r.get("menu_id")),
'count': int(r.get("count", 0))
})
# Sort by count descending
user_favs.sort(key=lambda x: x['count'], reverse=True)
# Ambil detail menu
result = []
for fav in user_favs[:limit]:
menu = menu_get(fav['menu_id'])
if menu:
result.append({
'menu': menu,
'count': fav['count']
})
return result
# Wilayah dikuasai UI
class App:
def __init__(self, root):
self.root = root
self.root.title("Cafe Totoro Mania")
self.session = None
self.img_cache = {}
self.cart = []
self.setup_ui()
def setup_ui(self):
self.root.geometry("1000x650")
self.root.resizable(False, False)
self.login_frame()
def login_frame(self):
for w in self.root.winfo_children():
w.destroy()
frame = ttk.Frame(self.root, padding=20)
frame.pack(expand=True)
ttk.Label(frame, text="Login kak", font=("Arial", 24)).grid(row=0, column=0, columnspan=2, pady=10)
ttk.Label(frame, text="Username:").grid(row=1, column=0, sticky='e', pady=5)
self.username_var = tk.StringVar()
ttk.Entry(frame, textvariable=self.username_var, width=30).grid(row=1, column=1, pady=5)
ttk.Label(frame, text="Password:").grid(row=2, column=0, sticky='e', pady=5)
self.password_var = tk.StringVar()
ttk.Entry(frame, textvariable=self.password_var, show="*", width=30).grid(row=2, column=1, pady=5)
ttk.Button(frame, text="Login", command=self.handle_login).grid(row=3, column=0, columnspan=2, pady=12)
def handle_login(self):
u = self.username_var.get().strip()
p = self.password_var.get().strip()
if not u or not p:
messagebox.showwarning("Input", "Masukkan username & password")
return
user = authenticate(u,p)
if not user:
messagebox.showerror("Gagal", "Username atau password salah")
return
self.session = user
messagebox.showinfo("Sukses", f"Login berhasil sebagai {user['role']}")
self.dashboard_frame()
def logout(self):
self.session = None
self.img_cache.clear()
self.cart = []
self.login_frame()
def dashboard_frame(self):
for w in self.root.winfo_children():
w.destroy()
top = ttk.Frame(self.root)
top.pack(fill='x')
ttk.Label(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)
main = ttk.Notebook(self.root)
main.pack(fill='both', expand=True, padx=10, pady=8)
self.tab_menu_manage = ttk.Frame(main)
self.tab_menu_view = ttk.Frame(main)
self.tab_promo = ttk.Frame(main)
main.add(self.tab_menu_view, text="Menu - View")
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)
if self.session['role'] == 'admin':
self.build_menu_manage_tab(self.tab_menu_manage)
self.build_promo_tab(self.tab_promo)
def build_menu_view_tab(self, parent):
for w in parent.winfo_children():
w.destroy()
left = ttk.Frame(parent, width=600)
right = ttk.Frame(parent, width=380)
left.pack(side='left', fill='both', expand=True, padx=6, pady=6)
right.pack(side='right', fill='y', padx=6, pady=6)
filter_frame = ttk.Frame(left)
filter_frame.pack(fill='x', pady=6)
ttk.Label(filter_frame, text="Cari / Nama atau Kategori:").pack(side='left', padx=3)
self.view_search_var = tk.StringVar()
ttk.Entry(filter_frame, textvariable=self.view_search_var, width=30).pack(side='left', padx=3)
ttk.Button(filter_frame, text="Cari", command=self.reload_view_table).pack(side='left', padx=3)
ttk.Button(filter_frame, text="Reset", command=self.reset_view_filters).pack(side='left', padx=3)
ttk.Button(filter_frame, text="Hanya Tersedia", command=lambda: self.reload_view_table(available_only=True)).pack(side='left', padx=6)
cols = ("ID","Nama","Kategori","Harga","Stok","Tersedia","ItemDisc%")
self.view_tree = ttk.Treeview(left, columns=cols, show='headings', height=18)
for c in cols:
self.view_tree.heading(c, text=c)
self.view_tree.column(c, width=90 if c!="Nama" else 200)
self.view_tree.pack(fill='both', expand=True)
self.view_tree.bind("<<TreeviewSelect>>", self.on_view_select)
ttk.Label(right, text="Preview Item", font=("Arial", 12, "bold")).pack(pady=6)
self.preview_label = ttk.Label(right, text="Pilih menu di kiri")
self.preview_label.pack()
self.preview_img_label = ttk.Label(right)
self.preview_img_label.pack(pady=6)
self.preview_detail = tk.Text(right, width=45, height=12)
self.preview_detail.pack()
self.reload_view_table()
def reload_view_table(self, available_only=False):
s = self.view_search_var.get().strip() if hasattr(self, 'view_search_var') else ""
results = menu_list(search_text=s or None, available_only=available_only)
for r in self.view_tree.get_children():
self.view_tree.delete(r)
for row in results:
mid,nama,kategori,harga,stok,foto,tersedia,item_disc = row
self.view_tree.insert("", tk.END, values=(mid,nama,kategori,harga,stok, "Yes" if tersedia else "No", item_disc))
def reset_view_filters(self):
self.view_search_var.set("")
self.reload_view_table()
def on_view_select(self, event):
sel = self.view_tree.selection()
if not sel:
return
item = self.view_tree.item(sel)['values']
menu_id = item[0]
data = menu_get(menu_id)
if not data:
return
mid,nama,kategori,harga,stok,foto,tersedia,item_disc = data
self.preview_detail.delete('1.0', tk.END)
txt = f"ID: {mid}\nNama: {nama}\nKategori: {kategori}\nHarga: {harga}\nStok: {stok}\nTersedia: {'Yes' if tersedia else 'No'}\nItem Discount: {item_disc}%\nFoto path: {foto}\n"
self.preview_detail.insert(tk.END, txt)
if foto and os.path.exists(foto):
try:
img = Image.open(foto)
img.thumbnail(IMG_PREVIEW_SIZE)
tkimg = ImageTk.PhotoImage(img)
self.img_cache['preview'] = tkimg
self.preview_img_label.config(image=tkimg)
except Exception as e:
self.preview_img_label.config(image='')
else:
self.preview_img_label.config(image='')
def build_menu_manage_tab(self, parent):
for w in parent.winfo_children():
w.destroy()
topfrm = ttk.Frame(parent)
topfrm.pack(fill='x', padx=6, pady=6)
ttk.Label(topfrm, text="Kelola Menu", font=("Arial", 14, "bold")).pack(side='left')
ttk.Button(topfrm, text="Tambah Menu", command=self.open_add_menu_window).pack(side='right', padx=6)
cols = ("ID","Nama","Kategori","Harga","Stok","Tersedia","ItemDisc%")
self.manage_tree = ttk.Treeview(parent, columns=cols, show='headings', height=18)
for c in cols:
self.manage_tree.heading(c, text=c)
self.manage_tree.column(c, width=100 if c!="Nama" else 220)
self.manage_tree.pack(fill='both', padx=6, pady=6)
btnfrm = ttk.Frame(parent)
btnfrm.pack(pady=6)
ttk.Button(btnfrm, text="Edit Terpilih", command=self.open_edit_menu_window).pack(side='left', padx=6)
ttk.Button(btnfrm, text="Hapus Terpilih", command=self.delete_selected_menu).pack(side='left', padx=6)
ttk.Button(btnfrm, text="Reload", command=self.reload_manage_table).pack(side='left', padx=6)
self.reload_manage_table()
def reload_manage_table(self):
for r in self.manage_tree.get_children():
self.manage_tree.delete(r)
rows = menu_list()
for row in rows:
mid,nama,kategori,harga,stok,foto,tersedia,item_disc = row
self.manage_tree.insert("", tk.END, values=(mid,nama,kategori,harga,stok,"Yes" if tersedia else "No", item_disc))
def open_add_menu_window(self):
w = tk.Toplevel(self.root)
w.title("Tambah Menu")
frm = ttk.Frame(w,padding=10)
frm.pack()
labels = ["Nama","Kategori","Harga","Stok","Foto path","Item Discount (%)"]
vars = {}
for i,lab in enumerate(labels):
ttk.Label(frm, text=lab).grid(row=i, column=0, sticky='e', pady=4)
vars[lab] = tk.StringVar()
ttk.Entry(frm, textvariable=vars[lab], width=40).grid(row=i, column=1, pady=4)
ttk.Button(frm, text="Pilih Foto", command=lambda: self.select_file(vars["Foto path"])).grid(row=4, column=2, padx=6)
def save():
try:
nama = vars["Nama"].get().strip()
kategori = vars["Kategori"].get().strip()
harga = float(vars["Harga"].get())
stok = int(vars["Stok"].get())
foto = vars["Foto path"].get().strip() or None
item_disc = float(vars["Item Discount (%)"].get() or 0)
except Exception as e:
messagebox.showerror("Input error", "Periksa kembali input (Harga/Stok harus angka)")
return
menu_add(nama,kategori,harga,stok,foto,item_disc)
messagebox.showinfo("Sukses","Menu ditambahkan")
w.destroy()
self.reload_manage_table()
self.reload_view_table()
ttk.Button(frm, text="Simpan", command=save).grid(row=len(labels), column=1, pady=8)
def open_edit_menu_window(self):
sel = self.manage_tree.selection()
if not sel:
messagebox.showwarning("Pilih", "Pilih menu terlebih dahulu")
return
item = self.manage_tree.item(sel)['values']
menu_id = item[0]
data = menu_get(menu_id)
if not data:
messagebox.showerror("Error", "Data menu tidak ditemukan")
return
mid,nama,kategori,harga,stok,foto,tersedia,item_disc = data
w = tk.Toplevel(self.root)
w.title("Edit Menu")
frm = ttk.Frame(w,padding=10)
frm.pack()
labels = ["Nama","Kategori","Harga","Stok","Foto path","Item Discount (%)"]
vars = {}
defaults = [nama,kategori,str(harga),str(stok),foto or "",str(item_disc or 0)]
for i,lab in enumerate(labels):
ttk.Label(frm, text=lab).grid(row=i, column=0, sticky='e', pady=4)
vars[lab] = tk.StringVar(value=defaults[i])
ttk.Entry(frm, textvariable=vars[lab], width=40).grid(row=i, column=1, pady=4)
ttk.Button(frm, text="Pilih Foto", command=lambda: self.select_file(vars["Foto path"])).grid(row=4, column=2, padx=6)
def save():
try:
nama = vars["Nama"].get().strip()
kategori = vars["Kategori"].get().strip()
harga = float(vars["Harga"].get())
stok = int(vars["Stok"].get())
foto = vars["Foto path"].get().strip() or None
item_disc = float(vars["Item Discount (%)"].get() or 0)
except:
messagebox.showerror("Input error", "Periksa input")
return
menu_update(menu_id, nama, kategori, harga, stok, foto, item_disc)
messagebox.showinfo("Sukses","Menu diperbarui")
w.destroy()
self.reload_manage_table()
self.reload_view_table()
ttk.Button(frm, text="Update", command=save).grid(row=len(labels), column=1, pady=8)
def delete_selected_menu(self):
sel = self.manage_tree.selection()
if not sel:
messagebox.showwarning("Pilih", "Pilih menu untuk dihapus")
return
item = self.manage_tree.item(sel)['values']
menu_id = item[0]
if messagebox.askyesno("Konfirmasi", "Hapus menu terpilih?"):
menu_delete(menu_id)
messagebox.showinfo("Dihapus", "Menu berhasil dihapus")
self.reload_manage_table()
self.reload_view_table()
def select_file(self, var):
p = filedialog.askopenfilename(title="Pilih file gambar",
filetypes=[("Image files","*.png;*.jpg;*.jpeg;*.gif;*.bmp"),("All files","*.*")])
if p:
var.set(p)
def build_promo_tab(self, parent):
for w in parent.winfo_children():
w.destroy()
top = ttk.Frame(parent)
top.pack(fill='x', pady=6)
ttk.Label(top, text="Promo Codes", font=("Arial", 14, "bold")).pack(side='left', padx=6)
ttk.Button(top, text="Tambah Promo", command=self.open_add_promo).pack(side='right', padx=6)
cols = ("Code","Type","Value","MinTotal")
self.promo_tree = ttk.Treeview(parent, columns=cols, show='headings', height=12)
for c in cols:
self.promo_tree.heading(c, text=c)
self.promo_tree.column(c, width=120)
self.promo_tree.pack(fill='x', padx=6, pady=6)
btnfrm = ttk.Frame(parent)
btnfrm.pack(pady=6)
ttk.Button(btnfrm, text="Edit Promo", command=self.open_edit_promo).pack(side='left', padx=6)
ttk.Button(btnfrm, text="Hapus Promo", command=self.delete_selected_promo).pack(side='left', padx=6)
ttk.Button(btnfrm, text="Reload", command=self.reload_promo_table).pack(side='left', padx=6)
self.reload_promo_table()
def reload_promo_table(self):
for r in self.promo_tree.get_children():
self.promo_tree.delete(r)
for p in promo_list():
self.promo_tree.insert("", tk.END, values=p)
def open_add_promo(self):
w = tk.Toplevel(self.root)
w.title("Tambah Promo")
w.geometry("350x230")
w.transient(self.root)
w.grab_set()
frm = ttk.Frame(w, padding=15)
frm.pack(fill="both", expand=True)
vars = {
'code': tk.StringVar(),
'type': tk.StringVar(value='percent'),
'value': tk.StringVar(),
'min_total': tk.StringVar(value='0')
}
ttk.Label(frm, text="Code:").grid(row=0, column=0, sticky='e', pady=5)
ttk.Entry(frm, textvariable=vars['code'], width=20).grid(row=0, column=1)
ttk.Label(frm, text="Type (percent/fixed):").grid(row=1, column=0, sticky='e', pady=5)
ttk.Entry(frm, textvariable=vars['type'], width=20).grid(row=1, column=1)
ttk.Label(frm, text="Value:").grid(row=2, column=0, sticky='e', pady=5)
ttk.Entry(frm, textvariable=vars['value'], width=20).grid(row=2, column=1)
ttk.Label(frm, text="Min Total:").grid(row=3, column=0, sticky='e', pady=5)
ttk.Entry(frm, textvariable=vars['min_total'], width=20).grid(row=3, column=1)
def save():
try:
code = vars['code'].get().strip().upper()
ptype = vars['type'].get().strip()
val = float(vars['value'].get())
mt = float(vars['min_total'].get() or 0)
if ptype not in ('percent', 'fixed'):
raise ValueError("type harus 'percent' atau 'fixed'")
except Exception as e:
messagebox.showerror("Error", f"Input salah: {e}")
return
try:
promo_add(code, ptype, val, mt)
messagebox.showinfo("Sukses", "Promo ditambahkan")
w.destroy()
self.reload_promo_table()
except Exception as e:
messagebox.showerror("Error", f"Kode promo sudah ada atau error: {e}")
ttk.Button(frm, text="Simpan", command=save).grid(row=4, column=1, pady=12)
def open_edit_promo(self):
sel = self.promo_tree.selection()
if not sel:
messagebox.showwarning("Pilih", "Pilih promo untuk diedit")
return
code = self.promo_tree.item(sel)['values'][0]
row = promo_get(code)
if not row:
messagebox.showerror("Error", "Promo tidak ditemukan")
return
code, ptype, val, min_total = row
w = tk.Toplevel(self.root)
w.title("Edit Promo")
w.geometry("350x230")
w.transient(self.root)
w.grab_set()
frm = ttk.Frame(w, padding=15)
frm.pack(fill="both", expand=True)
vars = {
'type': tk.StringVar(value=ptype),
'value': tk.StringVar(value=str(val)),
'min_total': tk.StringVar(value=str(min_total))
}
ttk.Label(frm, text=f"Code: {code}", font=("Arial", 10, "bold")).grid(row=0, column=0, columnspan=2, pady=5)
ttk.Label(frm, text="Type (percent/fixed):").grid(row=1, column=0, sticky='e', pady=5)
ttk.Entry(frm, textvariable=vars['type'], width=20).grid(row=1, column=1)
ttk.Label(frm, text="Value:").grid(row=2, column=0, sticky='e', pady=5)
ttk.Entry(frm, textvariable=vars['value'], width=20).grid(row=2, column=1)
ttk.Label(frm, text="Min Total:").grid(row=3, column=0, sticky='e', pady=5)
ttk.Entry(frm, textvariable=vars['min_total'], width=20).grid(row=3, column=1)
def save():
try:
ptype = vars['type'].get().strip()
val = float(vars['value'].get())
mt = float(vars['min_total'].get() or 0)
if ptype not in ('percent', 'fixed'):
raise ValueError("type harus 'percent' atau 'fixed'")
except Exception as e:
messagebox.showerror("Error", f"Input salah: {e}")
return
promo_update(code, ptype, val, mt)
messagebox.showinfo("Sukses", "Promo diperbarui")
w.destroy()
self.reload_promo_table()
ttk.Button(frm, text="Update", command=save).grid(row=4, column=1, pady=12)
def delete_selected_promo(self):
sel = self.promo_tree.selection()
if not sel:
messagebox.showwarning("Pilih", "Pilih promo")
return
code = self.promo_tree.item(sel)['values'][0]
if messagebox.askyesno("Konfirmasi", f"Hapus promo {code}?"):
promo_delete(code)
messagebox.showinfo("Dihapus","Promo terhapus")
self.reload_promo_table()
# Done
if __name__ == "__main__":
init_db_csv()
root = tk.Tk()
app = App(root)
root.mainloop()