Sistem Order Pembeli
This commit is contained in:
parent
1f904a2ba8
commit
5f8f54e8d2
@ -1 +1 @@
|
|||||||
id,transaksi_id,menu_id,qty,harga_satuan,subtotal_item
|
id,transaksi_id,menu_id,qty,harga_satuan,subtotal_item
|
||||||
|
|||||||
|
540
main.py
540
main.py
@ -9,7 +9,7 @@
|
|||||||
import os
|
import os
|
||||||
import csv
|
import csv
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk, messagebox, filedialog
|
from tkinter import ttk, messagebox, filedialog, simpledialog
|
||||||
from PIL import Image, ImageTk
|
from PIL import Image, ImageTk
|
||||||
|
|
||||||
USERS_CSV = "users.csv"
|
USERS_CSV = "users.csv"
|
||||||
@ -365,6 +365,187 @@ def promo_get(code):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Wilayah dikuasai Transaksi
|
||||||
|
|
||||||
|
|
||||||
|
def transaksi_add(user_id, nomor_meja, cart_items, promo_code=None):
|
||||||
|
"""Simpan transaksi baru dengan status 'pending'"""
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
if not cart_items:
|
||||||
|
return False, "Keranjang kosong"
|
||||||
|
|
||||||
|
# Hitung diskon dan total
|
||||||
|
calc = apply_discounts_and_promo(cart_items, promo_code)
|
||||||
|
|
||||||
|
# Buat transaksi
|
||||||
|
rows = read_all(TRANSAKSI_CSV)
|
||||||
|
transaksi_id = next_int_id(rows, "id")
|
||||||
|
tanggal = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
transaksi_row = {
|
||||||
|
"id": transaksi_id,
|
||||||
|
"user_id": str(user_id),
|
||||||
|
"nomor_meja": str(nomor_meja),
|
||||||
|
"total": str(calc['total']),
|
||||||
|
"status": "pending",
|
||||||
|
"promo_code": calc.get('promo_code') or "",
|
||||||
|
"subtotal": str(calc['subtotal']),
|
||||||
|
"item_discount": str(calc['item_discount']),
|
||||||
|
"promo_discount": str(calc['promo_discount']),
|
||||||
|
"tanggal": tanggal
|
||||||
|
}
|
||||||
|
rows.append(transaksi_row)
|
||||||
|
write_all(TRANSAKSI_CSV, ["id", "user_id", "nomor_meja", "total", "status", "promo_code", "subtotal", "item_discount", "promo_discount", "tanggal"], rows)
|
||||||
|
|
||||||
|
# Simpan detail transaksi
|
||||||
|
detail_rows = read_all(DETAIL_TRANSAKSI_CSV)
|
||||||
|
for item in cart_items:
|
||||||
|
detail_id = next_int_id(detail_rows, "id")
|
||||||
|
menu_data = menu_get(item['menu_id'])
|
||||||
|
if not menu_data:
|
||||||
|
continue
|
||||||
|
_, nama, kategori, harga, stok, foto, tersedia, item_disc = menu_data
|
||||||
|
|
||||||
|
qty = int(item['qty'])
|
||||||
|
subtotal_item = harga * qty
|
||||||
|
|
||||||
|
detail_rows.append({
|
||||||
|
"id": detail_id,
|
||||||
|
"transaksi_id": transaksi_id,
|
||||||
|
"menu_id": str(item['menu_id']),
|
||||||
|
"qty": str(qty),
|
||||||
|
"harga_satuan": str(harga),
|
||||||
|
"subtotal_item": str(subtotal_item)
|
||||||
|
})
|
||||||
|
|
||||||
|
# Kurangi stok
|
||||||
|
success, msg = menu_decrease_stock(item['menu_id'], qty)
|
||||||
|
if not success:
|
||||||
|
return False, f"Gagal mengurangi stok menu ID {item['menu_id']}: {msg}"
|
||||||
|
|
||||||
|
write_all(DETAIL_TRANSAKSI_CSV, ["id", "transaksi_id", "menu_id", "qty", "harga_satuan", "subtotal_item"], detail_rows)
|
||||||
|
|
||||||
|
return True, transaksi_id
|
||||||
|
|
||||||
|
|
||||||
|
def transaksi_list(status=None, user_id=None):
|
||||||
|
"""Ambil daftar transaksi, bisa filter by status atau user_id"""
|
||||||
|
rows = read_all(TRANSAKSI_CSV)
|
||||||
|
out = []
|
||||||
|
for r in rows:
|
||||||
|
if status and r.get("status") != status:
|
||||||
|
continue
|
||||||
|
if user_id and r.get("user_id") != str(user_id):
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
tid = int(r.get("id") or 0)
|
||||||
|
except:
|
||||||
|
tid = r.get("id")
|
||||||
|
try:
|
||||||
|
uid = int(r.get("user_id") or 0)
|
||||||
|
except:
|
||||||
|
uid = r.get("user_id")
|
||||||
|
try:
|
||||||
|
meja = int(r.get("nomor_meja") or 0)
|
||||||
|
except:
|
||||||
|
meja = r.get("nomor_meja")
|
||||||
|
try:
|
||||||
|
total = float(r.get("total") or 0.0)
|
||||||
|
except:
|
||||||
|
total = 0.0
|
||||||
|
|
||||||
|
out.append((tid, uid, meja, total, r.get("status"), r.get("promo_code"), r.get("tanggal")))
|
||||||
|
|
||||||
|
out.sort(key=lambda x: int(x[0]), reverse=True)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def transaksi_get(transaksi_id):
|
||||||
|
"""Ambil detail transaksi by ID"""
|
||||||
|
rows = read_all(TRANSAKSI_CSV)
|
||||||
|
for r in rows:
|
||||||
|
if r.get("id") == str(transaksi_id):
|
||||||
|
try:
|
||||||
|
tid = int(r.get("id") or 0)
|
||||||
|
except:
|
||||||
|
tid = r.get("id")
|
||||||
|
try:
|
||||||
|
uid = int(r.get("user_id") or 0)
|
||||||
|
except:
|
||||||
|
uid = r.get("user_id")
|
||||||
|
try:
|
||||||
|
meja = int(r.get("nomor_meja") or 0)
|
||||||
|
except:
|
||||||
|
meja = r.get("nomor_meja")
|
||||||
|
try:
|
||||||
|
total = float(r.get("total") or 0.0)
|
||||||
|
except:
|
||||||
|
total = 0.0
|
||||||
|
try:
|
||||||
|
subtotal = float(r.get("subtotal") or 0.0)
|
||||||
|
except:
|
||||||
|
subtotal = 0.0
|
||||||
|
try:
|
||||||
|
item_disc = float(r.get("item_discount") or 0.0)
|
||||||
|
except:
|
||||||
|
item_disc = 0.0
|
||||||
|
try:
|
||||||
|
promo_disc = float(r.get("promo_discount") or 0.0)
|
||||||
|
except:
|
||||||
|
promo_disc = 0.0
|
||||||
|
|
||||||
|
return (tid, uid, meja, total, r.get("status"), r.get("promo_code"), subtotal, item_disc, promo_disc, r.get("tanggal"))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def transaksi_update_status(transaksi_id, new_status):
|
||||||
|
"""Update status transaksi"""
|
||||||
|
rows = read_all(TRANSAKSI_CSV)
|
||||||
|
found = False
|
||||||
|
for r in rows:
|
||||||
|
if r.get("id") == str(transaksi_id):
|
||||||
|
r["status"] = new_status
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if found:
|
||||||
|
write_all(TRANSAKSI_CSV, ["id", "user_id", "nomor_meja", "total", "status", "promo_code", "subtotal", "item_discount", "promo_discount", "tanggal"], rows)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def detail_transaksi_list(transaksi_id):
|
||||||
|
"""Ambil semua detail item dari transaksi tertentu"""
|
||||||
|
rows = read_all(DETAIL_TRANSAKSI_CSV)
|
||||||
|
out = []
|
||||||
|
for r in rows:
|
||||||
|
if r.get("transaksi_id") == str(transaksi_id):
|
||||||
|
try:
|
||||||
|
did = int(r.get("id") or 0)
|
||||||
|
except:
|
||||||
|
did = r.get("id")
|
||||||
|
try:
|
||||||
|
mid = int(r.get("menu_id") or 0)
|
||||||
|
except:
|
||||||
|
mid = r.get("menu_id")
|
||||||
|
try:
|
||||||
|
qty = int(r.get("qty") or 0)
|
||||||
|
except:
|
||||||
|
qty = 0
|
||||||
|
try:
|
||||||
|
harga = float(r.get("harga_satuan") or 0.0)
|
||||||
|
except:
|
||||||
|
harga = 0.0
|
||||||
|
try:
|
||||||
|
subtotal = float(r.get("subtotal_item") or 0.0)
|
||||||
|
except:
|
||||||
|
subtotal = 0.0
|
||||||
|
|
||||||
|
out.append((did, mid, qty, harga, subtotal))
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -463,6 +644,8 @@ class App:
|
|||||||
self.root.geometry("1000x650")
|
self.root.geometry("1000x650")
|
||||||
self.root.resizable(False, False)
|
self.root.resizable(False, False)
|
||||||
self.login_frame()
|
self.login_frame()
|
||||||
|
style = ttk.Style()
|
||||||
|
style.configure("Accent.TButton", font=("Arial", 11, "bold"))
|
||||||
|
|
||||||
def login_frame(self):
|
def login_frame(self):
|
||||||
for w in self.root.winfo_children():
|
for w in self.root.winfo_children():
|
||||||
@ -514,7 +697,9 @@ class App:
|
|||||||
self.tab_menu_manage = ttk.Frame(main)
|
self.tab_menu_manage = ttk.Frame(main)
|
||||||
self.tab_menu_view = ttk.Frame(main)
|
self.tab_menu_view = ttk.Frame(main)
|
||||||
self.tab_promo = ttk.Frame(main)
|
self.tab_promo = ttk.Frame(main)
|
||||||
|
self.tab_order = ttk.Frame(main)
|
||||||
|
|
||||||
|
main.add(self.tab_order, text="Order Menu")
|
||||||
main.add(self.tab_menu_view, text="Menu - View")
|
main.add(self.tab_menu_view, text="Menu - View")
|
||||||
if self.session['role'] == 'admin':
|
if self.session['role'] == 'admin':
|
||||||
main.add(self.tab_menu_manage, text="Menu - Manage")
|
main.add(self.tab_menu_manage, text="Menu - Manage")
|
||||||
@ -523,6 +708,7 @@ class App:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
self.build_menu_view_tab(self.tab_menu_view)
|
self.build_menu_view_tab(self.tab_menu_view)
|
||||||
|
self.build_order_tab(self.tab_order)
|
||||||
if self.session['role'] == 'admin':
|
if self.session['role'] == 'admin':
|
||||||
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)
|
||||||
@ -866,7 +1052,359 @@ class App:
|
|||||||
promo_delete(code)
|
promo_delete(code)
|
||||||
messagebox.showinfo("Dihapus","Promo terhapus")
|
messagebox.showinfo("Dihapus","Promo terhapus")
|
||||||
self.reload_promo_table()
|
self.reload_promo_table()
|
||||||
|
|
||||||
|
def build_order_tab(self, parent):
|
||||||
|
"""Tab untuk order menu dengan tampilan card seperti GrabFood/Gojek"""
|
||||||
|
for w in parent.winfo_children():
|
||||||
|
w.destroy()
|
||||||
|
|
||||||
|
# Split jadi 2 panel: kiri = menu cards, kanan = cart
|
||||||
|
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='both', padx=6, pady=6)
|
||||||
|
|
||||||
|
# === PANEL KIRI: Daftar Menu dengan Card ===
|
||||||
|
ttk.Label(left, text="Daftar Menu", font=("Arial", 13, "bold")).pack(pady=6)
|
||||||
|
|
||||||
|
# Filter search
|
||||||
|
search_frame = ttk.Frame(left)
|
||||||
|
search_frame.pack(fill='x', pady=4)
|
||||||
|
ttk.Label(search_frame, text="Cari:").pack(side='left', padx=3)
|
||||||
|
self.order_search_var = tk.StringVar()
|
||||||
|
ttk.Entry(search_frame, textvariable=self.order_search_var, width=25).pack(side='left', padx=3)
|
||||||
|
ttk.Button(search_frame, text="Cari", command=self.reload_order_menu_cards).pack(side='left', padx=3)
|
||||||
|
ttk.Button(search_frame, text="Reset", command=self.reset_order_search).pack(side='left', padx=3)
|
||||||
|
|
||||||
|
# Scrollable frame untuk cards
|
||||||
|
canvas = tk.Canvas(left, bg='white')
|
||||||
|
scrollbar = ttk.Scrollbar(left, orient="vertical", command=canvas.yview)
|
||||||
|
self.menu_cards_frame = ttk.Frame(canvas)
|
||||||
|
|
||||||
|
self.menu_cards_frame.bind(
|
||||||
|
"<Configure>",
|
||||||
|
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
|
||||||
|
)
|
||||||
|
|
||||||
|
canvas.create_window((0, 0), window=self.menu_cards_frame, anchor="nw")
|
||||||
|
canvas.configure(yscrollcommand=scrollbar.set)
|
||||||
|
|
||||||
|
canvas.pack(side="left", fill="both", expand=True)
|
||||||
|
scrollbar.pack(side="right", fill="y")
|
||||||
|
|
||||||
|
# Bind mouse wheel untuk scroll
|
||||||
|
def _on_mousewheel(event):
|
||||||
|
canvas.yview_scroll(int(-1*(event.delta/120)), "units")
|
||||||
|
canvas.bind_all("<MouseWheel>", _on_mousewheel)
|
||||||
|
|
||||||
|
# === PANEL KANAN: Keranjang ===
|
||||||
|
ttk.Label(right, text="Keranjang Belanja", font=("Arial", 13, "bold")).pack(pady=6)
|
||||||
|
|
||||||
|
# Treeview cart
|
||||||
|
cart_cols = ("Menu", "Qty", "Harga", "Subtotal")
|
||||||
|
self.cart_tree = ttk.Treeview(right, columns=cart_cols, show='headings', height=12)
|
||||||
|
for c in cart_cols:
|
||||||
|
self.cart_tree.heading(c, text=c)
|
||||||
|
if c == "Menu":
|
||||||
|
self.cart_tree.column(c, width=150)
|
||||||
|
else:
|
||||||
|
self.cart_tree.column(c, width=70)
|
||||||
|
self.cart_tree.pack(fill='both', expand=True)
|
||||||
|
|
||||||
|
# Tombol hapus item atau kosongkan cart
|
||||||
|
cart_btn_frame = ttk.Frame(right)
|
||||||
|
cart_btn_frame.pack(pady=4)
|
||||||
|
ttk.Button(cart_btn_frame, text="Hapus Item", command=self.remove_cart_item).pack(side='left', padx=3)
|
||||||
|
ttk.Button(cart_btn_frame, text="Kosongkan", command=self.clear_cart).pack(side='left', padx=3)
|
||||||
|
|
||||||
|
# Info total
|
||||||
|
self.cart_subtotal_label = ttk.Label(right, text="Subtotal: Rp 0", font=("Arial", 10))
|
||||||
|
self.cart_subtotal_label.pack(pady=2)
|
||||||
|
self.cart_discount_label = ttk.Label(right, text="Diskon Item: Rp 0", font=("Arial", 10))
|
||||||
|
self.cart_discount_label.pack(pady=2)
|
||||||
|
self.cart_promo_label = ttk.Label(right, text="Diskon Promo: Rp 0", font=("Arial", 10))
|
||||||
|
self.cart_promo_label.pack(pady=2)
|
||||||
|
self.cart_total_label = ttk.Label(right, text="TOTAL: Rp 0", font=("Arial", 12, "bold"))
|
||||||
|
self.cart_total_label.pack(pady=4)
|
||||||
|
|
||||||
|
# Input nomor meja dan promo
|
||||||
|
checkout_frame = ttk.Frame(right)
|
||||||
|
checkout_frame.pack(pady=6)
|
||||||
|
ttk.Label(checkout_frame, text="No. Meja:").grid(row=0, column=0, sticky='e', padx=3, pady=3)
|
||||||
|
self.order_meja_var = tk.StringVar()
|
||||||
|
ttk.Entry(checkout_frame, textvariable=self.order_meja_var, width=15).grid(row=0, column=1, pady=3)
|
||||||
|
|
||||||
|
ttk.Label(checkout_frame, text="Kode Promo:").grid(row=1, column=0, sticky='e', padx=3, pady=3)
|
||||||
|
self.order_promo_var = tk.StringVar()
|
||||||
|
ttk.Entry(checkout_frame, textvariable=self.order_promo_var, width=15).grid(row=1, column=1, pady=3)
|
||||||
|
ttk.Button(checkout_frame, text="Terapkan", command=self.update_cart_display).grid(row=1, column=2, padx=3)
|
||||||
|
|
||||||
|
# Tombol checkout
|
||||||
|
ttk.Button(right, text="🛒 CHECKOUT", command=self.checkout_order, style="Accent.TButton").pack(pady=8)
|
||||||
|
|
||||||
|
# Init cart data
|
||||||
|
self.cart_items = [] # List of dict: {'menu_id': int, 'qty': int}
|
||||||
|
|
||||||
|
# Load menu cards
|
||||||
|
self.reload_order_menu_cards()
|
||||||
|
|
||||||
|
def reload_order_menu_cards(self):
|
||||||
|
"""Load menu dalam bentuk cards dengan gambar + tombol +/-"""
|
||||||
|
# Clear existing cards
|
||||||
|
for widget in self.menu_cards_frame.winfo_children():
|
||||||
|
widget.destroy()
|
||||||
|
|
||||||
|
# Get menu data
|
||||||
|
search = self.order_search_var.get().strip() or None
|
||||||
|
results = menu_list(search_text=search, available_only=True)
|
||||||
|
|
||||||
|
# Buat dict untuk qty di cart
|
||||||
|
cart_dict = {}
|
||||||
|
for cart_item in self.cart_items:
|
||||||
|
cart_dict[cart_item['menu_id']] = cart_item['qty']
|
||||||
|
|
||||||
|
# Render cards dalam grid (2 kolom)
|
||||||
|
row = 0
|
||||||
|
col = 0
|
||||||
|
for menu_data in results:
|
||||||
|
mid, nama, kategori, harga, stok, foto, tersedia, item_disc = menu_data
|
||||||
|
|
||||||
|
# Create card frame
|
||||||
|
card = tk.Frame(self.menu_cards_frame, relief='ridge', borderwidth=2, bg='white', padx=10, pady=10)
|
||||||
|
card.grid(row=row, column=col, padx=8, pady=8, sticky='nsew')
|
||||||
|
|
||||||
|
# Gambar
|
||||||
|
if foto and os.path.exists(foto):
|
||||||
|
try:
|
||||||
|
img = Image.open(foto)
|
||||||
|
img = img.resize((150, 100), Image.Resampling.LANCZOS)
|
||||||
|
photo = ImageTk.PhotoImage(img)
|
||||||
|
|
||||||
|
# Simpan reference agar tidak di-garbage collect
|
||||||
|
img_label = tk.Label(card, image=photo, bg='white')
|
||||||
|
img_label.image = photo
|
||||||
|
img_label.pack()
|
||||||
|
except:
|
||||||
|
tk.Label(card, text="[No Image]", bg='lightgray', width=20, height=6).pack()
|
||||||
|
else:
|
||||||
|
tk.Label(card, text="[No Image]", bg='lightgray', width=20, height=6).pack()
|
||||||
|
|
||||||
|
# Nama menu
|
||||||
|
tk.Label(card, text=nama, font=("Arial", 11, "bold"), bg='white', wraplength=150).pack(pady=(5, 2))
|
||||||
|
|
||||||
|
# Kategori
|
||||||
|
tk.Label(card, text=kategori, font=("Arial", 9), fg='gray', bg='white').pack()
|
||||||
|
|
||||||
|
# Harga
|
||||||
|
harga_text = f"Rp {harga:,.0f}"
|
||||||
|
if item_disc > 0:
|
||||||
|
harga_text += f" (-{item_disc}%)"
|
||||||
|
tk.Label(card, text=harga_text, font=("Arial", 10, "bold"), fg='green', bg='white').pack(pady=2)
|
||||||
|
|
||||||
|
# Stok info
|
||||||
|
tk.Label(card, text=f"Stok: {stok}", font=("Arial", 8), fg='blue', bg='white').pack(pady=2)
|
||||||
|
|
||||||
|
# Tombol +/- atau + saja
|
||||||
|
qty_in_cart = cart_dict.get(mid, 0)
|
||||||
|
|
||||||
|
btn_frame = tk.Frame(card, bg='white')
|
||||||
|
btn_frame.pack(pady=5)
|
||||||
|
|
||||||
|
if qty_in_cart > 0:
|
||||||
|
# Tampilkan - [qty] +
|
||||||
|
tk.Button(
|
||||||
|
btn_frame,
|
||||||
|
text="➖",
|
||||||
|
font=("Arial", 12, "bold"),
|
||||||
|
bg='#FF5722',
|
||||||
|
fg='white',
|
||||||
|
width=3,
|
||||||
|
command=lambda m=mid: self.decrease_from_card(m)
|
||||||
|
).pack(side='left', padx=2)
|
||||||
|
|
||||||
|
tk.Label(
|
||||||
|
btn_frame,
|
||||||
|
text=str(qty_in_cart),
|
||||||
|
font=("Arial", 12, "bold"),
|
||||||
|
bg='white',
|
||||||
|
width=3
|
||||||
|
).pack(side='left', padx=5)
|
||||||
|
|
||||||
|
tk.Button(
|
||||||
|
btn_frame,
|
||||||
|
text="➕",
|
||||||
|
font=("Arial", 12, "bold"),
|
||||||
|
bg='#4CAF50',
|
||||||
|
fg='white',
|
||||||
|
width=3,
|
||||||
|
command=lambda m=mid, s=stok: self.increase_from_card(m, s)
|
||||||
|
).pack(side='left', padx=2)
|
||||||
|
else:
|
||||||
|
# Tampilkan tombol + aja
|
||||||
|
tk.Button(
|
||||||
|
btn_frame,
|
||||||
|
text="➕ Tambah",
|
||||||
|
font=("Arial", 10, "bold"),
|
||||||
|
bg='#4CAF50',
|
||||||
|
fg='white',
|
||||||
|
width=12,
|
||||||
|
command=lambda m=mid, s=stok: self.increase_from_card(m, s)
|
||||||
|
).pack()
|
||||||
|
|
||||||
|
# Next column
|
||||||
|
col += 1
|
||||||
|
if col >= 2: # 2 cards per row
|
||||||
|
col = 0
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
def reset_order_search(self):
|
||||||
|
self.order_search_var.set("")
|
||||||
|
self.reload_order_menu_cards()
|
||||||
|
|
||||||
|
def increase_from_card(self, menu_id, stok):
|
||||||
|
"""Tambah qty dari tombol + di card"""
|
||||||
|
# Cek qty saat ini
|
||||||
|
current_qty = 0
|
||||||
|
cart_item_found = None
|
||||||
|
for cart_item in self.cart_items:
|
||||||
|
if cart_item['menu_id'] == menu_id:
|
||||||
|
current_qty = cart_item['qty']
|
||||||
|
cart_item_found = cart_item
|
||||||
|
break
|
||||||
|
|
||||||
|
# Cek stok
|
||||||
|
if current_qty >= stok:
|
||||||
|
messagebox.showwarning("Stok Habis", f"Stok hanya tersisa {stok}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Tambah qty
|
||||||
|
if cart_item_found:
|
||||||
|
cart_item_found['qty'] += 1
|
||||||
|
else:
|
||||||
|
self.cart_items.append({'menu_id': menu_id, 'qty': 1})
|
||||||
|
|
||||||
|
# Update tampilan
|
||||||
|
self.reload_order_menu_cards()
|
||||||
|
self.update_cart_display()
|
||||||
|
|
||||||
|
def decrease_from_card(self, menu_id):
|
||||||
|
"""Kurangi qty dari tombol - di card"""
|
||||||
|
for i, cart_item in enumerate(self.cart_items):
|
||||||
|
if cart_item['menu_id'] == menu_id:
|
||||||
|
cart_item['qty'] -= 1
|
||||||
|
|
||||||
|
# Kalau qty jadi 0, hapus dari cart
|
||||||
|
if cart_item['qty'] <= 0:
|
||||||
|
del self.cart_items[i]
|
||||||
|
|
||||||
|
# Update tampilan
|
||||||
|
self.reload_order_menu_cards()
|
||||||
|
self.update_cart_display()
|
||||||
|
return
|
||||||
|
|
||||||
|
def update_cart_display(self):
|
||||||
|
"""Update tampilan keranjang dan hitung total"""
|
||||||
|
|
||||||
|
# Clear tree
|
||||||
|
for r in self.cart_tree.get_children():
|
||||||
|
self.cart_tree.delete(r)
|
||||||
|
|
||||||
|
# Tampilkan item
|
||||||
|
for cart_item in self.cart_items:
|
||||||
|
menu_data = menu_get(cart_item['menu_id'])
|
||||||
|
if not menu_data:
|
||||||
|
print(f"WARNING: Menu ID {cart_item['menu_id']} tidak ditemukan!")
|
||||||
|
continue
|
||||||
|
|
||||||
|
_, nama, kategori, harga, stok, foto, tersedia, item_disc = menu_data
|
||||||
|
qty = cart_item['qty']
|
||||||
|
subtotal = harga * qty
|
||||||
|
|
||||||
|
# DEBUG
|
||||||
|
print(f"Menambahkan ke tree: {nama} x{qty} = {subtotal}")
|
||||||
|
|
||||||
|
self.cart_tree.insert("", tk.END, values=(nama, qty, f"{harga:,.0f}", f"{subtotal:,.0f}"))
|
||||||
|
|
||||||
|
# Hitung total dengan diskon
|
||||||
|
promo_code = self.order_promo_var.get().strip() or None
|
||||||
|
|
||||||
|
if self.cart_items: # PENTING: Hanya hitung kalau ada item
|
||||||
|
calc = apply_discounts_and_promo(self.cart_items, promo_code)
|
||||||
|
|
||||||
|
self.cart_subtotal_label.config(text=f"Subtotal: Rp {calc['subtotal']:,.0f}")
|
||||||
|
self.cart_discount_label.config(text=f"Diskon Item: Rp {calc['item_discount']:,.0f}")
|
||||||
|
self.cart_promo_label.config(text=f"Diskon Promo: Rp {calc['promo_discount']:,.0f}")
|
||||||
|
self.cart_total_label.config(text=f"TOTAL: Rp {calc['total']:,.0f}")
|
||||||
|
else:
|
||||||
|
# Reset label kalau cart kosong
|
||||||
|
self.cart_subtotal_label.config(text="Subtotal: Rp 0")
|
||||||
|
self.cart_discount_label.config(text="Diskon Item: Rp 0")
|
||||||
|
self.cart_promo_label.config(text="Diskon Promo: Rp 0")
|
||||||
|
self.cart_total_label.config(text="TOTAL: Rp 0")
|
||||||
|
|
||||||
|
|
||||||
|
def remove_cart_item(self):
|
||||||
|
"""Hapus item dari cart"""
|
||||||
|
sel = self.cart_tree.selection()
|
||||||
|
if not sel:
|
||||||
|
messagebox.showwarning("Pilih Item", "Pilih item di keranjang")
|
||||||
|
return
|
||||||
|
|
||||||
|
idx = self.cart_tree.index(sel)
|
||||||
|
if idx >= len(self.cart_items):
|
||||||
|
return
|
||||||
|
|
||||||
|
del self.cart_items[idx]
|
||||||
|
self.update_cart_display()
|
||||||
|
|
||||||
|
def clear_cart(self):
|
||||||
|
"""Kosongkan keranjang"""
|
||||||
|
if not self.cart_items:
|
||||||
|
return
|
||||||
|
|
||||||
|
if messagebox.askyesno("Konfirmasi", "Kosongkan keranjang?"):
|
||||||
|
self.cart_items = []
|
||||||
|
self.update_cart_display()
|
||||||
|
|
||||||
|
def checkout_order(self):
|
||||||
|
"""Simpan pesanan ke database"""
|
||||||
|
if not self.cart_items:
|
||||||
|
messagebox.showwarning("Keranjang Kosong", "Tambahkan item terlebih dahulu")
|
||||||
|
return
|
||||||
|
|
||||||
|
nomor_meja = self.order_meja_var.get().strip()
|
||||||
|
if not nomor_meja:
|
||||||
|
messagebox.showwarning("Input Error", "Masukkan nomor meja")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
nomor_meja = int(nomor_meja)
|
||||||
|
except:
|
||||||
|
messagebox.showerror("Input Error", "Nomor meja harus angka")
|
||||||
|
return
|
||||||
|
|
||||||
|
promo_code = self.order_promo_var.get().strip() or None
|
||||||
|
|
||||||
|
# Validasi promo
|
||||||
|
if promo_code:
|
||||||
|
promo_data = promo_get(promo_code)
|
||||||
|
if not promo_data:
|
||||||
|
messagebox.showwarning("Promo Invalid", "Kode promo tidak ditemukan")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Simpan transaksi
|
||||||
|
success, result = transaksi_add(self.session['id'], nomor_meja, self.cart_items, promo_code)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
messagebox.showinfo("Sukses", f"Pesanan berhasil! ID Transaksi: {result}\nStatus: Pending")
|
||||||
|
# Reset
|
||||||
|
self.cart_items = []
|
||||||
|
self.order_meja_var.set("")
|
||||||
|
self.order_promo_var.set("")
|
||||||
|
self.update_cart_display()
|
||||||
|
self.reload_order_menu() # Refresh stok
|
||||||
|
else:
|
||||||
|
messagebox.showerror("Error", f"Gagal menyimpan pesanan: {result}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
4
menu.csv
4
menu.csv
@ -1,5 +1,5 @@
|
|||||||
id,nama,kategori,harga,stok,foto,tersedia,item_discount_pct
|
id,nama,kategori,harga,stok,foto,tersedia,item_discount_pct
|
||||||
1,Americano,Minuman,20000,10,,1,0
|
1,Americano,Minuman,20000,10,,1,0
|
||||||
2,Latte,Minuman,25000,5,,1,10
|
2,Latte,Minuman,25000,2,,1,10
|
||||||
3,Banana Cake,Dessert,30000,2,,1,0
|
3,Banana Cake,Dessert,30000,1,,1,0
|
||||||
4,Nasi Goreng,Makanan,35000,0,,0,0
|
4,Nasi Goreng,Makanan,35000,0,,0,0
|
||||||
|
|||||||
|
@ -1 +1 @@
|
|||||||
id,user_id,nomor_meja,total,status,promo_code,subtotal,item_discount,promo_discount,tanggal
|
id,user_id,nomor_meja,total,status,promo_code,subtotal,item_discount,promo_discount,tanggal
|
||||||
|
|||||||
|
Loading…
x
Reference in New Issue
Block a user