|
|
|
|
@ -130,6 +130,7 @@ def seed_defaults():
|
|
|
|
|
})
|
|
|
|
|
write_all(PROMO_CSV, ["code","type","value","min_total"], rows)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Seed meja (10 meja default)
|
|
|
|
|
meja_rows = read_all(MEJA_CSV)
|
|
|
|
|
if not meja_rows:
|
|
|
|
|
@ -736,6 +737,11 @@ class App:
|
|
|
|
|
# Tab khusus waiter
|
|
|
|
|
if self.session['role'] in ['waiter', 'admin']:
|
|
|
|
|
main.add(self.tab_waiter, text="Waiter - Pesanan")
|
|
|
|
|
|
|
|
|
|
# Tab pembayaran untuk kasir & admin
|
|
|
|
|
if self.session['role'] in ['kasir', 'admin']:
|
|
|
|
|
self.tab_payment = ttk.Frame(main)
|
|
|
|
|
main.add(self.tab_payment, text="💰 Pembayaran")
|
|
|
|
|
|
|
|
|
|
# Tab khusus admin
|
|
|
|
|
if self.session['role'] == 'admin':
|
|
|
|
|
@ -754,6 +760,9 @@ class App:
|
|
|
|
|
if self.session['role'] in ['waiter', 'admin']:
|
|
|
|
|
self.build_waiter_tab(self.tab_waiter)
|
|
|
|
|
|
|
|
|
|
if self.session['role'] in ['kasir', 'admin']:
|
|
|
|
|
self.build_payment_tab(self.tab_payment)
|
|
|
|
|
|
|
|
|
|
if self.session['role'] == 'admin':
|
|
|
|
|
self.build_menu_manage_tab(self.tab_menu_manage)
|
|
|
|
|
self.build_promo_tab(self.tab_promo)
|
|
|
|
|
@ -2110,13 +2119,513 @@ def favorite_all(limit=10):
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# FITUR PEMBAYARAN
|
|
|
|
|
|
|
|
|
|
def build_payment_tab(self, parent):
|
|
|
|
|
"""Tab pembayaran untuk kasir & admin"""
|
|
|
|
|
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="💰 Pembayaran Transaksi", font=("Arial", 14, "bold")).pack(side='left')
|
|
|
|
|
ttk.Button(header, text="🔄 Refresh", command=self.reload_payment_orders).pack(side='right', padx=6)
|
|
|
|
|
|
|
|
|
|
# Split 2 panel: kiri = daftar transaksi, kanan = form pembayaran
|
|
|
|
|
left = ttk.LabelFrame(parent, text="📋 Transaksi Siap Dibayar (Status: Selesai)", padding=10)
|
|
|
|
|
left.pack(side='left', fill='both', expand=True, padx=10, pady=6)
|
|
|
|
|
|
|
|
|
|
right = ttk.LabelFrame(parent, text="💳 Form Pembayaran", padding=10)
|
|
|
|
|
right.pack(side='right', fill='both', expand=True, padx=10, pady=6)
|
|
|
|
|
|
|
|
|
|
# === PANEL KIRI: Daftar Transaksi Selesai ===
|
|
|
|
|
|
|
|
|
|
# Treeview transaksi
|
|
|
|
|
cols = ("ID", "Meja", "Total", "Status", "Tanggal")
|
|
|
|
|
self.payment_tree = ttk.Treeview(left, columns=cols, show='headings', height=12)
|
|
|
|
|
|
|
|
|
|
self.payment_tree.heading("ID", text="ID")
|
|
|
|
|
self.payment_tree.heading("Meja", text="Meja")
|
|
|
|
|
self.payment_tree.heading("Total", text="Total")
|
|
|
|
|
self.payment_tree.heading("Status", text="Status")
|
|
|
|
|
self.payment_tree.heading("Tanggal", text="Tanggal")
|
|
|
|
|
|
|
|
|
|
self.payment_tree.column("ID", width=50)
|
|
|
|
|
self.payment_tree.column("Meja", width=60)
|
|
|
|
|
self.payment_tree.column("Total", width=100)
|
|
|
|
|
self.payment_tree.column("Status", width=80)
|
|
|
|
|
self.payment_tree.column("Tanggal", width=140)
|
|
|
|
|
|
|
|
|
|
self.payment_tree.pack(fill='both', expand=True, pady=6)
|
|
|
|
|
|
|
|
|
|
# Bind event
|
|
|
|
|
self.payment_tree.bind("<<TreeviewSelect>>", self.on_payment_select)
|
|
|
|
|
|
|
|
|
|
# Detail transaksi
|
|
|
|
|
detail_frame = ttk.Frame(left)
|
|
|
|
|
detail_frame.pack(fill='x', pady=6)
|
|
|
|
|
|
|
|
|
|
self.payment_detail_text = tk.Text(detail_frame, height=8, font=("Courier New", 8), wrap='word')
|
|
|
|
|
detail_scroll = ttk.Scrollbar(detail_frame, orient='vertical', command=self.payment_detail_text.yview)
|
|
|
|
|
self.payment_detail_text.configure(yscrollcommand=detail_scroll.set)
|
|
|
|
|
|
|
|
|
|
self.payment_detail_text.pack(side='left', fill='both', expand=True)
|
|
|
|
|
detail_scroll.pack(side='right', fill='y')
|
|
|
|
|
|
|
|
|
|
# === PANEL KANAN: Form Pembayaran ===
|
|
|
|
|
|
|
|
|
|
# Info transaksi terpilih
|
|
|
|
|
info_frame = ttk.Frame(right)
|
|
|
|
|
info_frame.pack(fill='x', pady=10)
|
|
|
|
|
|
|
|
|
|
self.selected_transaksi_label = ttk.Label(info_frame, text="Belum ada transaksi dipilih", font=("Arial", 10, "bold"), foreground='red')
|
|
|
|
|
self.selected_transaksi_label.pack()
|
|
|
|
|
|
|
|
|
|
self.selected_total_label = ttk.Label(info_frame, text="Total: Rp 0", font=("Arial", 12, "bold"), foreground='green')
|
|
|
|
|
self.selected_total_label.pack(pady=4)
|
|
|
|
|
|
|
|
|
|
ttk.Separator(right, orient='horizontal').pack(fill='x', pady=10)
|
|
|
|
|
|
|
|
|
|
# Pilih metode pembayaran
|
|
|
|
|
method_frame = ttk.Frame(right)
|
|
|
|
|
method_frame.pack(fill='x', pady=10)
|
|
|
|
|
|
|
|
|
|
ttk.Label(method_frame, text="💳 Pilih Metode Pembayaran:", font=("Arial", 10, "bold")).pack(anchor='w', pady=4)
|
|
|
|
|
|
|
|
|
|
self.payment_method_var = tk.StringVar(value='cash')
|
|
|
|
|
|
|
|
|
|
ttk.Radiobutton(method_frame, text="💵 Cash", variable=self.payment_method_var, value='cash', command=self.on_payment_method_change).pack(anchor='w', pady=2)
|
|
|
|
|
ttk.Radiobutton(method_frame, text="📱 QRIS", variable=self.payment_method_var, value='qris', command=self.on_payment_method_change).pack(anchor='w', pady=2)
|
|
|
|
|
ttk.Radiobutton(method_frame, text="💳 E-Wallet (GoPay/OVO/Dana)", variable=self.payment_method_var, value='ewallet', command=self.on_payment_method_change).pack(anchor='w', pady=2)
|
|
|
|
|
|
|
|
|
|
ttk.Separator(right, orient='horizontal').pack(fill='x', pady=10)
|
|
|
|
|
|
|
|
|
|
# Frame dinamis untuk input (berganti sesuai metode)
|
|
|
|
|
self.payment_input_frame = ttk.Frame(right)
|
|
|
|
|
self.payment_input_frame.pack(fill='both', expand=True, pady=10)
|
|
|
|
|
|
|
|
|
|
# Init cash input (default)
|
|
|
|
|
self.build_cash_input()
|
|
|
|
|
|
|
|
|
|
# Tombol proses pembayaran
|
|
|
|
|
ttk.Button(right, text="✅ PROSES PEMBAYARAN", command=self.process_payment, style="Accent.TButton").pack(pady=15, ipadx=20, ipady=10)
|
|
|
|
|
|
|
|
|
|
# Load data
|
|
|
|
|
self.reload_payment_orders()
|
|
|
|
|
|
|
|
|
|
def reload_payment_orders(self):
|
|
|
|
|
"""Load transaksi dengan status 'selesai' yang belum dibayar"""
|
|
|
|
|
# Clear tree
|
|
|
|
|
for r in self.payment_tree.get_children():
|
|
|
|
|
self.payment_tree.delete(r)
|
|
|
|
|
|
|
|
|
|
# Get transaksi selesai
|
|
|
|
|
orders = transaksi_list(status='selesai')
|
|
|
|
|
|
|
|
|
|
# Filter yang belum ada pembayaran
|
|
|
|
|
for order in orders:
|
|
|
|
|
tid, uid, meja, total, status, promo_code, tanggal = order
|
|
|
|
|
|
|
|
|
|
# Cek apakah sudah ada pembayaran
|
|
|
|
|
payment_data = pembayaran_get_by_transaksi(tid)
|
|
|
|
|
if payment_data:
|
|
|
|
|
continue # Skip jika sudah dibayar
|
|
|
|
|
|
|
|
|
|
self.payment_tree.insert("", tk.END, values=(
|
|
|
|
|
tid, meja, f"Rp {total:,.0f}", status, tanggal
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
def on_payment_select(self, event):
|
|
|
|
|
"""Tampilkan detail transaksi saat dipilih"""
|
|
|
|
|
sel = self.payment_tree.selection()
|
|
|
|
|
if not sel:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
item = self.payment_tree.item(sel)['values']
|
|
|
|
|
transaksi_id = item[0]
|
|
|
|
|
|
|
|
|
|
# Get detail transaksi
|
|
|
|
|
transaksi_data = transaksi_get(transaksi_id)
|
|
|
|
|
if not transaksi_data:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
tid, uid, meja, total, status, promo_code, subtotal, item_disc, promo_disc, tanggal = transaksi_data
|
|
|
|
|
detail_items = detail_transaksi_list(transaksi_id)
|
|
|
|
|
|
|
|
|
|
# Update label
|
|
|
|
|
self.selected_transaksi_label.config(text=f"Transaksi #{tid} - Meja {meja}", foreground='blue')
|
|
|
|
|
self.selected_total_label.config(text=f"Total: Rp {total:,.0f}")
|
|
|
|
|
|
|
|
|
|
# Format detail
|
|
|
|
|
detail_text = f"═══════════════════════════════════════════════\n"
|
|
|
|
|
detail_text += f"TRANSAKSI #{tid}\n"
|
|
|
|
|
detail_text += f"═══════════════════════════════════════════════\n\n"
|
|
|
|
|
detail_text += f"Meja : {meja}\n"
|
|
|
|
|
detail_text += f"Tanggal : {tanggal}\n"
|
|
|
|
|
detail_text += f"Status : {status.upper()}\n"
|
|
|
|
|
detail_text += f"Promo : {promo_code if promo_code else '-'}\n\n"
|
|
|
|
|
|
|
|
|
|
detail_text += f"───────────────────────────────────────────────\n"
|
|
|
|
|
detail_text += f"ITEM PESANAN:\n"
|
|
|
|
|
detail_text += f"───────────────────────────────────────────────\n"
|
|
|
|
|
|
|
|
|
|
for detail in detail_items:
|
|
|
|
|
did, mid, qty, harga, subtotal_item = detail
|
|
|
|
|
menu_data = menu_get(mid)
|
|
|
|
|
if menu_data:
|
|
|
|
|
_, nama, kategori, _, _, _, _, _ = menu_data
|
|
|
|
|
detail_text += f"• {nama}\n"
|
|
|
|
|
detail_text += f" {qty} x Rp {harga:,.0f} = Rp {subtotal_item:,.0f}\n\n"
|
|
|
|
|
|
|
|
|
|
detail_text += f"───────────────────────────────────────────────\n"
|
|
|
|
|
detail_text += f"Subtotal : Rp {subtotal:,.0f}\n"
|
|
|
|
|
detail_text += f"Diskon : Rp {item_disc + promo_disc:,.0f}\n"
|
|
|
|
|
detail_text += f"───────────────────────────────────────────────\n"
|
|
|
|
|
detail_text += f"TOTAL BAYAR : Rp {total:,.0f}\n"
|
|
|
|
|
detail_text += f"═══════════════════════════════════════════════\n"
|
|
|
|
|
|
|
|
|
|
self.payment_detail_text.delete('1.0', tk.END)
|
|
|
|
|
self.payment_detail_text.insert('1.0', detail_text)
|
|
|
|
|
|
|
|
|
|
def on_payment_method_change(self):
|
|
|
|
|
"""Ganti input form sesuai metode pembayaran"""
|
|
|
|
|
method = self.payment_method_var.get()
|
|
|
|
|
|
|
|
|
|
# Clear frame
|
|
|
|
|
for w in self.payment_input_frame.winfo_children():
|
|
|
|
|
w.destroy()
|
|
|
|
|
|
|
|
|
|
if method == 'cash':
|
|
|
|
|
self.build_cash_input()
|
|
|
|
|
elif method == 'qris':
|
|
|
|
|
self.build_qris_input()
|
|
|
|
|
elif method == 'ewallet':
|
|
|
|
|
self.build_ewallet_input()
|
|
|
|
|
|
|
|
|
|
def build_cash_input(self):
|
|
|
|
|
"""Form input untuk pembayaran cash"""
|
|
|
|
|
ttk.Label(self.payment_input_frame, text="💵 Pembayaran Cash", font=("Arial", 10, "bold")).pack(pady=6)
|
|
|
|
|
|
|
|
|
|
input_frame = ttk.Frame(self.payment_input_frame)
|
|
|
|
|
input_frame.pack(fill='x', pady=6)
|
|
|
|
|
|
|
|
|
|
ttk.Label(input_frame, text="Jumlah Bayar:").grid(row=0, column=0, sticky='w', pady=4)
|
|
|
|
|
self.cash_amount_var = tk.StringVar()
|
|
|
|
|
ttk.Entry(input_frame, textvariable=self.cash_amount_var, width=20).grid(row=0, column=1, pady=4, sticky='ew')
|
|
|
|
|
|
|
|
|
|
input_frame.columnconfigure(1, weight=1)
|
|
|
|
|
|
|
|
|
|
# Label kembalian
|
|
|
|
|
self.cash_change_label = ttk.Label(self.payment_input_frame, text="Kembalian: Rp 0", font=("Arial", 10), foreground='green')
|
|
|
|
|
self.cash_change_label.pack(pady=6)
|
|
|
|
|
|
|
|
|
|
# Bind event untuk hitung kembalian real-time
|
|
|
|
|
self.cash_amount_var.trace('w', self.calculate_cash_change)
|
|
|
|
|
|
|
|
|
|
def calculate_cash_change(self, *args):
|
|
|
|
|
"""Hitung kembalian cash secara real-time"""
|
|
|
|
|
sel = self.payment_tree.selection()
|
|
|
|
|
if not sel:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
item = self.payment_tree.item(sel)['values']
|
|
|
|
|
transaksi_id = item[0]
|
|
|
|
|
|
|
|
|
|
transaksi_data = transaksi_get(transaksi_id)
|
|
|
|
|
if not transaksi_data:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
total = transaksi_data[3]
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
cash_input = float(self.cash_amount_var.get() or 0)
|
|
|
|
|
change = cash_input - total
|
|
|
|
|
|
|
|
|
|
if change < 0:
|
|
|
|
|
self.cash_change_label.config(text=f"Kembalian: Kurang Rp {abs(change):,.0f}", foreground='red')
|
|
|
|
|
else:
|
|
|
|
|
self.cash_change_label.config(text=f"Kembalian: Rp {change:,.0f}", foreground='green')
|
|
|
|
|
except:
|
|
|
|
|
self.cash_change_label.config(text="Kembalian: Rp 0", foreground='gray')
|
|
|
|
|
|
|
|
|
|
def build_qris_input(self):
|
|
|
|
|
"""Form input untuk pembayaran QRIS (simulasi QR Code)"""
|
|
|
|
|
ttk.Label(self.payment_input_frame, text="📱 Pembayaran QRIS", font=("Arial", 10, "bold")).pack(pady=6)
|
|
|
|
|
|
|
|
|
|
ttk.Label(self.payment_input_frame, text="Scan QR Code di bawah untuk bayar:", font=("Arial", 9)).pack(pady=4)
|
|
|
|
|
|
|
|
|
|
# Generate QR Code
|
|
|
|
|
sel = self.payment_tree.selection()
|
|
|
|
|
if sel:
|
|
|
|
|
item = self.payment_tree.item(sel)['values']
|
|
|
|
|
transaksi_id = item[0]
|
|
|
|
|
|
|
|
|
|
transaksi_data = transaksi_get(transaksi_id)
|
|
|
|
|
if transaksi_data:
|
|
|
|
|
total = transaksi_data[3]
|
|
|
|
|
|
|
|
|
|
# QR Code data (simulasi)
|
|
|
|
|
qr_data = f"QRIS-CAFE-TOTORO-TRX{transaksi_id}-TOTAL{int(total)}"
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
import qrcode
|
|
|
|
|
qr = qrcode.QRCode(version=1, box_size=5, border=2)
|
|
|
|
|
qr.add_data(qr_data)
|
|
|
|
|
qr.make(fit=True)
|
|
|
|
|
|
|
|
|
|
qr_img = qr.make_image(fill_color="black", back_color="white")
|
|
|
|
|
qr_img = qr_img.resize((200, 200), Image.Resampling.LANCZOS)
|
|
|
|
|
|
|
|
|
|
qr_photo = ImageTk.PhotoImage(qr_img)
|
|
|
|
|
self.img_cache['qris'] = qr_photo
|
|
|
|
|
|
|
|
|
|
qr_label = ttk.Label(self.payment_input_frame, image=qr_photo)
|
|
|
|
|
qr_label.pack(pady=10)
|
|
|
|
|
|
|
|
|
|
except ImportError:
|
|
|
|
|
ttk.Label(self.payment_input_frame, text="[QR Code - Install qrcode library]", background='#e0e0e0', font=("Arial", 9)).pack(pady=10)
|
|
|
|
|
|
|
|
|
|
ttk.Label(self.payment_input_frame, text="⚠️ Ini adalah SIMULASI QRIS", font=("Arial", 8), foreground='orange').pack(pady=6)
|
|
|
|
|
ttk.Label(self.payment_input_frame, text="Klik 'PROSES PEMBAYARAN' untuk konfirmasi", font=("Arial", 8)).pack()
|
|
|
|
|
|
|
|
|
|
def build_ewallet_input(self):
|
|
|
|
|
"""Form input untuk pembayaran E-Wallet (simulasi)"""
|
|
|
|
|
ttk.Label(self.payment_input_frame, text="💳 Pembayaran E-Wallet", font=("Arial", 10, "bold")).pack(pady=6)
|
|
|
|
|
|
|
|
|
|
ttk.Label(self.payment_input_frame, text="Pilih E-Wallet:", font=("Arial", 9)).pack(pady=4)
|
|
|
|
|
|
|
|
|
|
self.ewallet_type_var = tk.StringVar(value='gopay')
|
|
|
|
|
|
|
|
|
|
ewallet_frame = ttk.Frame(self.payment_input_frame)
|
|
|
|
|
ewallet_frame.pack(pady=6)
|
|
|
|
|
|
|
|
|
|
ttk.Radiobutton(ewallet_frame, text="GoPay", variable=self.ewallet_type_var, value='gopay').pack(anchor='w', pady=2)
|
|
|
|
|
ttk.Radiobutton(ewallet_frame, text="OVO", variable=self.ewallet_type_var, value='ovo').pack(anchor='w', pady=2)
|
|
|
|
|
ttk.Radiobutton(ewallet_frame, text="Dana", variable=self.ewallet_type_var, value='dana').pack(anchor='w', pady=2)
|
|
|
|
|
ttk.Radiobutton(ewallet_frame, text="ShopeePay", variable=self.ewallet_type_var, value='shopeepay').pack(anchor='w', pady=2)
|
|
|
|
|
|
|
|
|
|
ttk.Label(self.payment_input_frame, text="⚠️ Ini adalah SIMULASI E-Wallet", font=("Arial", 8), foreground='orange').pack(pady=6)
|
|
|
|
|
ttk.Label(self.payment_input_frame, text="Klik 'PROSES PEMBAYARAN' untuk konfirmasi", font=("Arial", 8)).pack()
|
|
|
|
|
|
|
|
|
|
def process_payment(self):
|
|
|
|
|
"""Proses pembayaran sesuai metode yang dipilih"""
|
|
|
|
|
# Cek apakah ada transaksi terpilih
|
|
|
|
|
sel = self.payment_tree.selection()
|
|
|
|
|
if not sel:
|
|
|
|
|
messagebox.showwarning("Pilih Transaksi", "Pilih transaksi yang akan dibayar")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
item = self.payment_tree.item(sel)['values']
|
|
|
|
|
transaksi_id = item[0]
|
|
|
|
|
|
|
|
|
|
transaksi_data = transaksi_get(transaksi_id)
|
|
|
|
|
if not transaksi_data:
|
|
|
|
|
messagebox.showerror("Error", "Data transaksi tidak ditemukan")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
tid, uid, meja, total, status, promo_code, subtotal, item_disc, promo_disc, tanggal = transaksi_data
|
|
|
|
|
|
|
|
|
|
# Get metode pembayaran
|
|
|
|
|
method = self.payment_method_var.get()
|
|
|
|
|
|
|
|
|
|
# Validasi & proses sesuai metode
|
|
|
|
|
if method == 'cash':
|
|
|
|
|
success, message = self.process_cash_payment(tid, total)
|
|
|
|
|
elif method == 'qris':
|
|
|
|
|
success, message = self.process_qris_payment(tid, total)
|
|
|
|
|
elif method == 'ewallet':
|
|
|
|
|
success, message = self.process_ewallet_payment(tid, total)
|
|
|
|
|
else:
|
|
|
|
|
messagebox.showerror("Error", "Metode pembayaran tidak valid")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if success:
|
|
|
|
|
# Generate struk
|
|
|
|
|
struk = self.generate_struk(tid)
|
|
|
|
|
|
|
|
|
|
# Update status transaksi jadi 'dibayar'
|
|
|
|
|
transaksi_update_status(tid, 'dibayar')
|
|
|
|
|
|
|
|
|
|
# Tutup meja
|
|
|
|
|
meja_tutup(meja)
|
|
|
|
|
|
|
|
|
|
# Tampilkan struk
|
|
|
|
|
self.show_struk(struk)
|
|
|
|
|
|
|
|
|
|
# Reload data
|
|
|
|
|
self.reload_payment_orders()
|
|
|
|
|
|
|
|
|
|
# Reset selection
|
|
|
|
|
self.selected_transaksi_label.config(text="Belum ada transaksi dipilih", foreground='red')
|
|
|
|
|
self.selected_total_label.config(text="Total: Rp 0")
|
|
|
|
|
self.payment_detail_text.delete('1.0', tk.END)
|
|
|
|
|
else:
|
|
|
|
|
messagebox.showerror("Pembayaran Gagal", message)
|
|
|
|
|
|
|
|
|
|
def process_cash_payment(self, transaksi_id, total):
|
|
|
|
|
"""Proses pembayaran cash"""
|
|
|
|
|
try:
|
|
|
|
|
cash_input = float(self.cash_amount_var.get() or 0)
|
|
|
|
|
except:
|
|
|
|
|
return False, "Jumlah bayar tidak valid"
|
|
|
|
|
|
|
|
|
|
if cash_input < total:
|
|
|
|
|
return False, f"Uang kurang! Total: Rp {total:,.0f}, Bayar: Rp {cash_input:,.0f}"
|
|
|
|
|
|
|
|
|
|
# Simpan pembayaran
|
|
|
|
|
pembayaran_add(transaksi_id, 'cash', cash_input, 'sukses', '')
|
|
|
|
|
|
|
|
|
|
return True, "Pembayaran cash berhasil"
|
|
|
|
|
|
|
|
|
|
def process_qris_payment(self, transaksi_id, total):
|
|
|
|
|
"""Proses pembayaran QRIS (simulasi)"""
|
|
|
|
|
# Konfirmasi
|
|
|
|
|
confirm = messagebox.askyesno("Konfirmasi QRIS",
|
|
|
|
|
f"Pembayaran QRIS sebesar Rp {total:,.0f}\n\n"
|
|
|
|
|
f"⚠️ SIMULASI: Anggap customer sudah scan QR Code\n\n"
|
|
|
|
|
f"Lanjutkan pembayaran?")
|
|
|
|
|
|
|
|
|
|
if not confirm:
|
|
|
|
|
return False, "Pembayaran dibatalkan"
|
|
|
|
|
|
|
|
|
|
# Simpan pembayaran
|
|
|
|
|
pembayaran_add(transaksi_id, 'qris', total, 'sukses', '')
|
|
|
|
|
|
|
|
|
|
return True, "Pembayaran QRIS berhasil"
|
|
|
|
|
|
|
|
|
|
def process_ewallet_payment(self, transaksi_id, total):
|
|
|
|
|
"""Proses pembayaran E-Wallet (simulasi)"""
|
|
|
|
|
ewallet_type = self.ewallet_type_var.get()
|
|
|
|
|
ewallet_name = ewallet_type.upper()
|
|
|
|
|
|
|
|
|
|
# Konfirmasi
|
|
|
|
|
confirm = messagebox.askyesno("Konfirmasi E-Wallet",
|
|
|
|
|
f"Pembayaran {ewallet_name} sebesar Rp {total:,.0f}\n\n"
|
|
|
|
|
f"⚠️ SIMULASI: Anggap customer sudah konfirmasi di app\n\n"
|
|
|
|
|
f"Lanjutkan pembayaran?")
|
|
|
|
|
|
|
|
|
|
if not confirm:
|
|
|
|
|
return False, "Pembayaran dibatalkan"
|
|
|
|
|
|
|
|
|
|
# Simpan pembayaran
|
|
|
|
|
pembayaran_add(transaksi_id, f'ewallet-{ewallet_type}', total, 'sukses', '')
|
|
|
|
|
|
|
|
|
|
return True, f"Pembayaran {ewallet_name} berhasil"
|
|
|
|
|
|
|
|
|
|
def generate_struk(self, transaksi_id):
|
|
|
|
|
"""Generate struk transaksi"""
|
|
|
|
|
transaksi_data = transaksi_get(transaksi_id)
|
|
|
|
|
if not transaksi_data:
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
tid, uid, meja, total, status, promo_code, subtotal, item_disc, promo_disc, tanggal = transaksi_data
|
|
|
|
|
detail_items = detail_transaksi_list(transaksi_id)
|
|
|
|
|
payment_data = pembayaran_get_by_transaksi(transaksi_id)
|
|
|
|
|
|
|
|
|
|
struk = "═════════════════════════════════════════\n"
|
|
|
|
|
struk += " CAFE TOTORO MANIA\n"
|
|
|
|
|
struk += " Jl. Raya Kampus No. 123, Surabaya\n"
|
|
|
|
|
struk += " Telp: 031-123456\n"
|
|
|
|
|
struk += "═════════════════════════════════════════\n\n"
|
|
|
|
|
struk += f"No. Transaksi : {tid}\n"
|
|
|
|
|
struk += f"Tanggal : {tanggal}\n"
|
|
|
|
|
struk += f"Meja : {meja}\n"
|
|
|
|
|
struk += f"Kasir : {self.session['username']}\n"
|
|
|
|
|
|
|
|
|
|
if payment_data:
|
|
|
|
|
metode = payment_data[1]
|
|
|
|
|
struk += f"Pembayaran : {metode.upper()}\n"
|
|
|
|
|
|
|
|
|
|
struk += "─────────────────────────────────────────\n"
|
|
|
|
|
struk += "ITEM PESANAN:\n"
|
|
|
|
|
struk += "─────────────────────────────────────────\n"
|
|
|
|
|
|
|
|
|
|
for detail in detail_items:
|
|
|
|
|
did, mid, qty, harga, subtotal_item = detail
|
|
|
|
|
menu_data = menu_get(mid)
|
|
|
|
|
if menu_data:
|
|
|
|
|
_, nama, kategori, _, _, _, _, _ = menu_data
|
|
|
|
|
struk += f"{nama}\n"
|
|
|
|
|
struk += f" {qty} x Rp {harga:,.0f}".ljust(30)
|
|
|
|
|
struk += f"Rp {subtotal_item:,.0f}\n"
|
|
|
|
|
|
|
|
|
|
struk += "─────────────────────────────────────────\n"
|
|
|
|
|
struk += f"Subtotal : Rp {subtotal:,.0f}\n"
|
|
|
|
|
|
|
|
|
|
if item_disc > 0:
|
|
|
|
|
struk += f"Diskon Item : Rp {item_disc:,.0f}\n"
|
|
|
|
|
|
|
|
|
|
if promo_disc > 0:
|
|
|
|
|
struk += f"Diskon Promo : Rp {promo_disc:,.0f}\n"
|
|
|
|
|
|
|
|
|
|
struk += "─────────────────────────────────────────\n"
|
|
|
|
|
struk += f"TOTAL BAYAR : Rp {total:,.0f}\n"
|
|
|
|
|
|
|
|
|
|
# Jika cash, tampilkan bayar & kembalian
|
|
|
|
|
if payment_data and payment_data[1] == 'cash':
|
|
|
|
|
jumlah_bayar = payment_data[2]
|
|
|
|
|
kembalian = jumlah_bayar - total
|
|
|
|
|
struk += f"Bayar : Rp {jumlah_bayar:,.0f}\n"
|
|
|
|
|
struk += f"Kembalian : Rp {kembalian:,.0f}\n"
|
|
|
|
|
|
|
|
|
|
struk += "═════════════════════════════════════════\n"
|
|
|
|
|
struk += " TERIMA KASIH ATAS KUNJUNGAN ANDA\n"
|
|
|
|
|
struk += " SAMPAI JUMPA LAGI!\n"
|
|
|
|
|
struk += "═════════════════════════════════════════\n"
|
|
|
|
|
|
|
|
|
|
return struk
|
|
|
|
|
|
|
|
|
|
def show_struk(self, struk):
|
|
|
|
|
"""Tampilkan struk dalam popup window"""
|
|
|
|
|
w = tk.Toplevel(self.root)
|
|
|
|
|
w.title("✅ Pembayaran Berhasil - Struk Transaksi")
|
|
|
|
|
w.geometry("500x600")
|
|
|
|
|
w.transient(self.root)
|
|
|
|
|
w.grab_set()
|
|
|
|
|
|
|
|
|
|
# Frame untuk struk
|
|
|
|
|
frm = ttk.Frame(w, padding=15)
|
|
|
|
|
frm.pack(fill='both', expand=True)
|
|
|
|
|
|
|
|
|
|
ttk.Label(frm, text="✅ PEMBAYARAN BERHASIL!", font=("Arial", 14, "bold"), foreground='green').pack(pady=10)
|
|
|
|
|
|
|
|
|
|
# Text widget untuk struk
|
|
|
|
|
struk_text = tk.Text(frm, width=50, height=28, font=("Courier New", 9), wrap='word')
|
|
|
|
|
struk_scroll = ttk.Scrollbar(frm, orient='vertical', command=struk_text.yview)
|
|
|
|
|
struk_text.configure(yscrollcommand=struk_scroll.set)
|
|
|
|
|
|
|
|
|
|
struk_text.insert('1.0', struk)
|
|
|
|
|
struk_text.config(state='disabled') # Read-only
|
|
|
|
|
|
|
|
|
|
struk_text.pack(side='left', fill='both', expand=True)
|
|
|
|
|
struk_scroll.pack(side='right', fill='y')
|
|
|
|
|
|
|
|
|
|
# Tombol
|
|
|
|
|
btn_frame = ttk.Frame(w)
|
|
|
|
|
btn_frame.pack(pady=10)
|
|
|
|
|
|
|
|
|
|
ttk.Button(btn_frame, text="🖨️ Print (Simulasi)", command=lambda: self.print_struk_simulation(struk)).pack(side='left', padx=6)
|
|
|
|
|
ttk.Button(btn_frame, text="✅ Tutup", command=w.destroy).pack(side='left', padx=6)
|
|
|
|
|
|
|
|
|
|
def print_struk_simulation(self, struk):
|
|
|
|
|
"""Simulasi print struk (save to file)"""
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
|
|
filename = f"struk_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
with open(filename, 'w', encoding='utf-8') as f:
|
|
|
|
|
f.write(struk)
|
|
|
|
|
|
|
|
|
|
messagebox.showinfo("Print Simulasi", f"Struk berhasil disimpan ke file:\n{filename}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
messagebox.showerror("Error", f"Gagal menyimpan struk: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Done
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
init_db_csv()
|
|
|
|
|
root = tk.Tk()
|
|
|
|
|
|