"""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 sqlite3 import os import tkinter as tk from tkinter import ttk, messagebox, filedialog from PIL import Image, ImageTk DB_PATH = "cafe_person1.db" IMG_PREVIEW_SIZE = (120, 80) # Baguan Database (ati ati) def init_db(): conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute(""" CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, password TEXT, role TEXT ) """) c.execute(""" CREATE TABLE IF NOT EXISTS menu ( id INTEGER PRIMARY KEY AUTOINCREMENT, nama TEXT NOT NULL, kategori TEXT, harga REAL NOT NULL, stok INTEGER DEFAULT 0, foto TEXT, tersedia INTEGER DEFAULT 1, item_discount_pct REAL DEFAULT 0 -- per item discount percent (like 10 for 10%) ) """) c.execute(""" CREATE TABLE IF NOT EXISTS promo ( code TEXT PRIMARY KEY, type TEXT CHECK(type IN ('percent','fixed')), value REAL, min_total REAL DEFAULT 0 ) """) conn.commit() seed_defaults(conn) return conn def seed_defaults(conn): c = conn.cursor() defaults = [ ('admin','admin123','admin'), ('kasir','kasir123','kasir'), ('waiter','waiter123','waiter'), ('user','user123','pembeli'), ('owner','owner123','pemilik'), ] for u,p,r in defaults: try: c.execute("INSERT INTO users (username,password,role) VALUES (?,?,?)", (u,p,r)) except sqlite3.IntegrityError: pass 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), ] for name,kategori,harga,stok,foto,tersedia,disc in sample: c.execute("SELECT id FROM menu WHERE nama=?", (name,)) if c.fetchone() is None: c.execute("""INSERT INTO menu (nama,kategori,harga,stok,foto,tersedia,item_discount_pct) VALUES (?,?,?,?,?,?,?)""", (name,kategori,harga,stok,foto,tersedia,disc)) promos = [ ('CAFE10','percent',10,0), ('HEMAT5000','fixed',5000,20000), ] for code,ptype,val,min_total in promos: try: c.execute("INSERT INTO promo (code,type,value,min_total) VALUES (?,?,?,?)", (code,ptype,val,min_total)) except sqlite3.IntegrityError: pass conn.commit() def authenticate(username, password): conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute("SELECT id,username,role FROM users WHERE username=? AND password=?", (username,password)) r = c.fetchone() conn.close() if r: return {'id':r[0],'username':r[1],'role':r[2]} return None # Bagian Menu wel def menu_add(nama, kategori, harga, stok, foto, item_discount_pct=0): conn = sqlite3.connect(DB_PATH) c = conn.cursor() tersedia = 1 if stok>0 else 0 c.execute("""INSERT INTO menu (nama,kategori,harga,stok,foto,tersedia,item_discount_pct) VALUES (?,?,?,?,?,?,?)""", (nama,kategori,harga,stok,foto,tersedia,item_discount_pct)) conn.commit() conn.close() def menu_update(menu_id, nama, kategori, harga, stok, foto, item_discount_pct=0): conn = sqlite3.connect(DB_PATH) c = conn.cursor() tersedia = 1 if stok>0 else 0 c.execute("""UPDATE menu SET nama=?,kategori=?,harga=?,stok=?,foto=?,tersedia=?,item_discount_pct=? WHERE id=?""", (nama,kategori,harga,stok,foto,tersedia,item_discount_pct, menu_id)) conn.commit() conn.close() def menu_delete(menu_id): conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute("DELETE FROM menu WHERE id=?", (menu_id,)) conn.commit() conn.close() def menu_list(kategori=None, available_only=False, search_text=None): conn = sqlite3.connect(DB_PATH) c = conn.cursor() q = "SELECT id,nama,kategori,harga,stok,foto,tersedia,item_discount_pct FROM menu" conditions = [] params = [] if kategori: conditions.append("kategori=?") params.append(kategori) if available_only: conditions.append("tersedia=1") if search_text: conditions.append("(nama LIKE ? OR kategori LIKE ?)") params += [f"%{search_text}%", f"%{search_text}%"] if conditions: q += " WHERE " + " AND ".join(conditions) q += " ORDER BY id ASC" c.execute(q, params) rows = c.fetchall() conn.close() return rows def menu_get(menu_id): conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute("SELECT id,nama,kategori,harga,stok,foto,tersedia,item_discount_pct FROM menu WHERE id=?", (menu_id,)) r = c.fetchone() conn.close() return r def menu_decrease_stock(menu_id, qty): conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute("SELECT stok FROM menu WHERE id=?", (menu_id,)) r = c.fetchone() if not r: conn.close() return False, "Menu tidak ditemukan" stok = r[0] if stok < qty: conn.close() return False, "Stok tidak cukup" newstok = stok - qty tersedia = 1 if newstok>0 else 0 c.execute("UPDATE menu SET stok=?, tersedia=? WHERE id=?", (newstok, tersedia, menu_id)) conn.commit() conn.close() return True, newstok # Bagian Promooo def promo_add(code, ptype, value, min_total=0): conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute("INSERT INTO promo (code,type,value,min_total) VALUES (?,?,?,?)", (code,ptype,value,min_total)) conn.commit() conn.close() def promo_update(code, ptype, value, min_total=0): conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute("UPDATE promo SET type=?, value=?, min_total=? WHERE code=?", (ptype,value,min_total,code)) conn.commit() conn.close() def promo_delete(code): conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute("DELETE FROM promo WHERE code=?", (code,)) conn.commit() conn.close() def promo_list(): conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute("SELECT code,type,value,min_total FROM promo ORDER BY code") rows = c.fetchall() conn.close() return rows def promo_get(code): conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute("SELECT code,type,value,min_total FROM promo WHERE code=?", (code,)) r = c.fetchone() conn.close() return r def apply_discounts_and_promo(cart_items, promo_code=None): """ cart_items: list of dicts: [{'menu_id':..,'qty':..}, ...] returns breakdown: subtotal, item_discount_total, promo_code, promo_discount, total - uses item_discount_pct from menu table - promo_code can be percent or fixed, with min_total """ conn = sqlite3.connect(DB_PATH) c = conn.cursor() subtotal = 0.0 item_discount_total = 0.0 for it in cart_items: c.execute("SELECT harga,item_discount_pct FROM menu WHERE id=?", (it['menu_id'],)) r = c.fetchone() if not r: continue price, item_disc_pct = r qty = 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: c.execute("SELECT type,value,min_total FROM promo WHERE code=?", (promo_code,)) row = c.fetchone() if row: ptype, val, min_total = row if subtotal >= min_total: 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 conn.close() 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) } # wilayah UI (universitas indonesia) class App: def __init__(self, root): self.root = root self.root.title("Cafe Totoro") self.session = None self.img_cache = {} self.setup_ui() def setup_ui(self): self.root.geometry("1000x650") self.root.resizable(False, False) self.login_frame() # windah batubara # tampilan login dan logout 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", 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.login_frame() # tampilan dashboard untuk admin dah ngantuk we 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) self.tab_tests = 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") main.add(self.tab_tests, text="Admin - Tests") 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) self.build_tests_tab(self.tab_tests) # Bagian Search dan Filter 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("<>", self.on_view_select) # Preview Image 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='') # bagian menu manage khusus admin 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)