diff --git a/main.py b/main.py index b6fb453..446dfcd 100644 --- a/main.py +++ b/main.py @@ -6,102 +6,134 @@ - owner / owner123 (role pemilik) """ - -import sqlite3 import os +import csv import tkinter as tk from tkinter import ttk, messagebox, filedialog from PIL import Image, ImageTk -DB_PATH = "cafe_person1.db" +USERS_CSV = "users.csv" +MENU_CSV = "menu.csv" +PROMO_CSV = "promo.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() - -# Baguan Database (ati ati) +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 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 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 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: + +def next_int_id(rows, id_field="id"): + max_id = 0 + for r in rows: 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() + 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"]) + + 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): - 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]} + 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 @@ -110,170 +142,291 @@ def authenticate(username, password): -# Bagian Menu wel + + + +# Wilayah dikuasai Menu 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() + 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): - 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() + 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): - conn = sqlite3.connect(DB_PATH) - c = conn.cursor() - c.execute("DELETE FROM menu WHERE id=?", (menu_id,)) - conn.commit() - conn.close() + 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): - 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 + 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): - 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 + 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): - 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() + 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" - 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() + write_all(MENU_CSV, ["id","nama","kategori","harga","stok","foto","tersedia","item_discount_pct"], rows) return True, newstok -# Bagian Promooo + + + + + + + + + + + + +# Reza balap liar + +# wilayah dikuasai promo + 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() + 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): - 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() + 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): - conn = sqlite3.connect(DB_PATH) - c = conn.cursor() - c.execute("DELETE FROM promo WHERE code=?", (code,)) - conn.commit() - conn.close() + 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(): - 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 + 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): - 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 + 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): - """ - 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 + menu_rows = read_all(MENU_CSV) + menu_dict = {r["id"]: r for r in menu_rows} + for it in cart_items: - c.execute("SELECT harga,item_discount_pct FROM menu WHERE id=?", (it['menu_id'],)) - r = c.fetchone() + mid = str(it.get('menu_id')) + r = menu_dict.get(mid) if not r: continue - price, item_disc_pct = r - qty = it.get('qty',1) + 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) + 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: + 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) + 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), + '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) + 'promo_discount': round(promo_discount, 2), + 'total': round(total, 2) } @@ -281,16 +434,25 @@ def apply_discounts_and_promo(cart_items, promo_code=None): -# wilayah UI (universitas indonesia) + + + + + + + + + +# Wilayah dikuasai UI class App: def __init__(self, root): self.root = root - self.root.title("Cafe Totoro") + self.root.title("Cafe Totoro Mania") self.session = None - self.img_cache = {} + self.img_cache = {} self.setup_ui() def setup_ui(self): @@ -298,18 +460,13 @@ class App: 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="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() @@ -335,17 +492,11 @@ class App: 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() @@ -372,13 +523,6 @@ class App: self.build_menu_manage_tab(self.tab_menu_manage) self.build_promo_tab(self.tab_promo) - - - - -# Bagian Search dan Filter - - def build_menu_view_tab(self, parent): for w in parent.winfo_children(): w.destroy() @@ -404,14 +548,6 @@ class App: 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() @@ -460,15 +596,6 @@ class App: 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() @@ -588,16 +715,6 @@ class App: if p: var.set(p) - - - - - - - -# Promo manage Khusus Admin - - def build_promo_tab(self, parent): for w in parent.winfo_children(): w.destroy() @@ -671,12 +788,11 @@ class App: messagebox.showinfo("Sukses", "Promo ditambahkan") w.destroy() self.reload_promo_table() - except sqlite3.IntegrityError: - messagebox.showerror("Error", "Kode promo sudah ada") + 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: @@ -736,7 +852,6 @@ class App: 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: @@ -750,10 +865,16 @@ class App: + + + + + +# Done + + if __name__ == "__main__": - init_conn = init_db() - init_conn.close() + init_db_csv() root = tk.Tk() app = App(root) root.mainloop() - diff --git a/menu.csv b/menu.csv new file mode 100644 index 0000000..e4f28ee --- /dev/null +++ b/menu.csv @@ -0,0 +1,5 @@ +id,nama,kategori,harga,stok,foto,tersedia,item_discount_pct +1,Americano,Minuman,20000,10,,1,0 +2,Latte,Minuman,25000,5,,1,10 +3,Banana Cake,Dessert,30000,2,,1,0 +4,Nasi Goreng,Makanan,35000,0,,0,0 diff --git a/promo.csv b/promo.csv new file mode 100644 index 0000000..a256801 --- /dev/null +++ b/promo.csv @@ -0,0 +1,3 @@ +code,type,value,min_total +PARDEDE,percent,10,0 +BOTAK,fixed,5000,20000 diff --git a/users.csv b/users.csv new file mode 100644 index 0000000..8c81a09 --- /dev/null +++ b/users.csv @@ -0,0 +1,6 @@ +id,username,password,role +1,admin,admin123,admin +2,kasir,kasir123,kasir +3,waiter,waiter123,waiter +4,user,user123,pembeli +5,owner,owner123,pemilik