DB to CSV
This commit is contained in:
parent
b1f67c9a3f
commit
388d55be6a
619
main.py
619
main.py
@ -6,102 +6,134 @@
|
|||||||
- owner / owner123 (role pemilik)
|
- owner / owner123 (role pemilik)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import sqlite3
|
|
||||||
import os
|
import os
|
||||||
|
import csv
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk, messagebox, filedialog
|
from tkinter import ttk, messagebox, filedialog
|
||||||
from PIL import Image, ImageTk
|
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)
|
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):
|
||||||
# Baguan Database (ati ati)
|
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():
|
def write_all(path, fieldnames, rows):
|
||||||
conn = sqlite3.connect(DB_PATH)
|
with open(path, "w", newline="", encoding="utf-8") as f:
|
||||||
c = conn.cursor()
|
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||||
c.execute("""
|
writer.writeheader()
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
writer.writerows(rows)
|
||||||
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()
|
def next_int_id(rows, id_field="id"):
|
||||||
defaults = [
|
max_id = 0
|
||||||
('admin','admin123','admin'),
|
for r in rows:
|
||||||
('kasir','kasir123','kasir'),
|
|
||||||
('waiter','waiter123','waiter'),
|
|
||||||
('user','user123','pembeli'),
|
|
||||||
('owner','owner123','pemilik'),
|
|
||||||
]
|
|
||||||
for u,p,r in defaults:
|
|
||||||
try:
|
try:
|
||||||
c.execute("INSERT INTO users (username,password,role) VALUES (?,?,?)", (u,p,r))
|
v = int(r.get(id_field, 0) or 0)
|
||||||
except sqlite3.IntegrityError:
|
if v > max_id:
|
||||||
pass
|
max_id = v
|
||||||
sample = [
|
except:
|
||||||
('Americano','Minuman',20000,10,None,1,0),
|
continue
|
||||||
('Latte','Minuman',25000,5,None,1,10),
|
return str(max_id + 1)
|
||||||
('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:
|
def init_db_csv():
|
||||||
c.execute("SELECT id FROM menu WHERE nama=?", (name,))
|
ensure_file(USERS_CSV, ["id", "username", "password", "role"])
|
||||||
if c.fetchone() is None:
|
ensure_file(MENU_CSV, ["id", "nama", "kategori", "harga", "stok", "foto", "tersedia", "item_discount_pct"])
|
||||||
c.execute("""INSERT INTO menu (nama,kategori,harga,stok,foto,tersedia,item_discount_pct)
|
ensure_file(PROMO_CSV, ["code", "type", "value", "min_total"])
|
||||||
VALUES (?,?,?,?,?,?,?)""", (name,kategori,harga,stok,foto,tersedia,disc))
|
|
||||||
promos = [
|
seed_defaults()
|
||||||
('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:
|
# buat masukin data/sample ke database csv
|
||||||
pass
|
|
||||||
conn.commit()
|
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):
|
def authenticate(username, password):
|
||||||
conn = sqlite3.connect(DB_PATH)
|
rows = read_all(USERS_CSV)
|
||||||
c = conn.cursor()
|
for r in rows:
|
||||||
c.execute("SELECT id,username,role FROM users WHERE username=? AND password=?", (username,password))
|
if r.get("username") == username and r.get("password") == password:
|
||||||
r = c.fetchone()
|
return {'id': int(r.get("id")), 'username': r.get("username"), 'role': r.get("role")}
|
||||||
conn.close()
|
|
||||||
if r:
|
|
||||||
return {'id':r[0],'username':r[1],'role':r[2]}
|
|
||||||
return None
|
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):
|
def menu_add(nama, kategori, harga, stok, foto, item_discount_pct=0):
|
||||||
conn = sqlite3.connect(DB_PATH)
|
rows = read_all(MENU_CSV)
|
||||||
c = conn.cursor()
|
new_id = next_int_id(rows, "id")
|
||||||
tersedia = 1 if stok>0 else 0
|
tersedia = "1" if int(stok) > 0 else "0"
|
||||||
c.execute("""INSERT INTO menu (nama,kategori,harga,stok,foto,tersedia,item_discount_pct)
|
rows.append({
|
||||||
VALUES (?,?,?,?,?,?,?)""", (nama,kategori,harga,stok,foto,tersedia,item_discount_pct))
|
"id": new_id,
|
||||||
conn.commit()
|
"nama": nama,
|
||||||
conn.close()
|
"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):
|
def menu_update(menu_id, nama, kategori, harga, stok, foto, item_discount_pct=0):
|
||||||
conn = sqlite3.connect(DB_PATH)
|
rows = read_all(MENU_CSV)
|
||||||
c = conn.cursor()
|
found = False
|
||||||
tersedia = 1 if stok>0 else 0
|
for r in rows:
|
||||||
c.execute("""UPDATE menu SET nama=?,kategori=?,harga=?,stok=?,foto=?,tersedia=?,item_discount_pct=?
|
if r.get("id") == str(menu_id):
|
||||||
WHERE id=?""", (nama,kategori,harga,stok,foto,tersedia,item_discount_pct, menu_id))
|
r["nama"] = nama
|
||||||
conn.commit()
|
r["kategori"] = kategori
|
||||||
conn.close()
|
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):
|
def menu_delete(menu_id):
|
||||||
conn = sqlite3.connect(DB_PATH)
|
rows = read_all(MENU_CSV)
|
||||||
c = conn.cursor()
|
newrows = [r for r in rows if r.get("id") != str(menu_id)]
|
||||||
c.execute("DELETE FROM menu WHERE id=?", (menu_id,))
|
write_all(MENU_CSV, ["id","nama","kategori","harga","stok","foto","tersedia","item_discount_pct"], newrows)
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
def menu_list(kategori=None, available_only=False, search_text=None):
|
def menu_list(kategori=None, available_only=False, search_text=None):
|
||||||
conn = sqlite3.connect(DB_PATH)
|
rows = read_all(MENU_CSV)
|
||||||
c = conn.cursor()
|
out = []
|
||||||
q = "SELECT id,nama,kategori,harga,stok,foto,tersedia,item_discount_pct FROM menu"
|
for r in rows:
|
||||||
conditions = []
|
if kategori and r.get("kategori") != kategori:
|
||||||
params = []
|
continue
|
||||||
if kategori:
|
if available_only and r.get("tersedia") != "1":
|
||||||
conditions.append("kategori=?")
|
continue
|
||||||
params.append(kategori)
|
if search_text:
|
||||||
if available_only:
|
s = search_text.lower()
|
||||||
conditions.append("tersedia=1")
|
if s not in (r.get("nama","").lower() or "") and s not in (r.get("kategori","").lower() or ""):
|
||||||
if search_text:
|
continue
|
||||||
conditions.append("(nama LIKE ? OR kategori LIKE ?)")
|
try:
|
||||||
params += [f"%{search_text}%", f"%{search_text}%"]
|
mid = int(r.get("id") or 0)
|
||||||
if conditions:
|
except:
|
||||||
q += " WHERE " + " AND ".join(conditions)
|
mid = r.get("id")
|
||||||
q += " ORDER BY id ASC"
|
try:
|
||||||
c.execute(q, params)
|
harga = float(r.get("harga") or 0.0)
|
||||||
rows = c.fetchall()
|
except:
|
||||||
conn.close()
|
harga = 0.0
|
||||||
return rows
|
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):
|
def menu_get(menu_id):
|
||||||
conn = sqlite3.connect(DB_PATH)
|
rows = read_all(MENU_CSV)
|
||||||
c = conn.cursor()
|
for r in rows:
|
||||||
c.execute("SELECT id,nama,kategori,harga,stok,foto,tersedia,item_discount_pct FROM menu WHERE id=?", (menu_id,))
|
if r.get("id") == str(menu_id):
|
||||||
r = c.fetchone()
|
try:
|
||||||
conn.close()
|
mid = int(r.get("id") or 0)
|
||||||
return r
|
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):
|
def menu_decrease_stock(menu_id, qty):
|
||||||
conn = sqlite3.connect(DB_PATH)
|
rows = read_all(MENU_CSV)
|
||||||
c = conn.cursor()
|
found = False
|
||||||
c.execute("SELECT stok FROM menu WHERE id=?", (menu_id,))
|
for r in rows:
|
||||||
r = c.fetchone()
|
if r.get("id") == str(menu_id):
|
||||||
if not r:
|
found = True
|
||||||
conn.close()
|
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"
|
return False, "Menu tidak ditemukan"
|
||||||
stok = r[0]
|
write_all(MENU_CSV, ["id","nama","kategori","harga","stok","foto","tersedia","item_discount_pct"], rows)
|
||||||
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
|
return True, newstok
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Bagian Promooo
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Reza balap liar
|
||||||
|
|
||||||
|
# wilayah dikuasai promo
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def promo_add(code, ptype, value, min_total=0):
|
def promo_add(code, ptype, value, min_total=0):
|
||||||
conn = sqlite3.connect(DB_PATH)
|
rows = read_all(PROMO_CSV)
|
||||||
c = conn.cursor()
|
for r in rows:
|
||||||
c.execute("INSERT INTO promo (code,type,value,min_total) VALUES (?,?,?,?)", (code,ptype,value,min_total))
|
if r.get("code") == code:
|
||||||
conn.commit()
|
raise ValueError("Kode promo sudah ada")
|
||||||
conn.close()
|
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):
|
def promo_update(code, ptype, value, min_total=0):
|
||||||
conn = sqlite3.connect(DB_PATH)
|
rows = read_all(PROMO_CSV)
|
||||||
c = conn.cursor()
|
found = False
|
||||||
c.execute("UPDATE promo SET type=?, value=?, min_total=? WHERE code=?", (ptype,value,min_total,code))
|
for r in rows:
|
||||||
conn.commit()
|
if r.get("code") == code:
|
||||||
conn.close()
|
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):
|
def promo_delete(code):
|
||||||
conn = sqlite3.connect(DB_PATH)
|
rows = read_all(PROMO_CSV)
|
||||||
c = conn.cursor()
|
newrows = [r for r in rows if r.get("code") != code]
|
||||||
c.execute("DELETE FROM promo WHERE code=?", (code,))
|
write_all(PROMO_CSV, ["code","type","value","min_total"], newrows)
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
def promo_list():
|
def promo_list():
|
||||||
conn = sqlite3.connect(DB_PATH)
|
rows = read_all(PROMO_CSV)
|
||||||
c = conn.cursor()
|
out = []
|
||||||
c.execute("SELECT code,type,value,min_total FROM promo ORDER BY code")
|
for r in rows:
|
||||||
rows = c.fetchall()
|
try:
|
||||||
conn.close()
|
val = float(r.get("value") or 0.0)
|
||||||
return rows
|
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):
|
def promo_get(code):
|
||||||
conn = sqlite3.connect(DB_PATH)
|
rows = read_all(PROMO_CSV)
|
||||||
c = conn.cursor()
|
for r in rows:
|
||||||
c.execute("SELECT code,type,value,min_total FROM promo WHERE code=?", (code,))
|
if r.get("code") == code:
|
||||||
r = c.fetchone()
|
try:
|
||||||
conn.close()
|
val = float(r.get("value") or 0.0)
|
||||||
return r
|
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):
|
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
|
subtotal = 0.0
|
||||||
item_discount_total = 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:
|
for it in cart_items:
|
||||||
c.execute("SELECT harga,item_discount_pct FROM menu WHERE id=?", (it['menu_id'],))
|
mid = str(it.get('menu_id'))
|
||||||
r = c.fetchone()
|
r = menu_dict.get(mid)
|
||||||
if not r:
|
if not r:
|
||||||
continue
|
continue
|
||||||
price, item_disc_pct = r
|
try:
|
||||||
qty = it.get('qty',1)
|
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
|
line = price * qty
|
||||||
subtotal += line
|
subtotal += line
|
||||||
if item_disc_pct and item_disc_pct > 0:
|
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_discount = 0.0
|
||||||
promo_applied = None
|
promo_applied = None
|
||||||
if promo_code:
|
if promo_code:
|
||||||
c.execute("SELECT type,value,min_total FROM promo WHERE code=?", (promo_code,))
|
p = promo_get(promo_code)
|
||||||
row = c.fetchone()
|
if p:
|
||||||
if row:
|
_, ptype, val, min_total = p
|
||||||
ptype, val, min_total = row
|
if subtotal >= (min_total or 0.0):
|
||||||
if subtotal >= min_total:
|
|
||||||
if ptype == 'percent':
|
if ptype == 'percent':
|
||||||
promo_discount = (subtotal - item_discount_total) * (val/100.0)
|
promo_discount = (subtotal - item_discount_total) * (val / 100.0)
|
||||||
else:
|
else:
|
||||||
promo_discount = val
|
promo_discount = val
|
||||||
promo_applied = promo_code
|
promo_applied = promo_code
|
||||||
|
|
||||||
total = subtotal - item_discount_total - promo_discount
|
total = subtotal - item_discount_total - promo_discount
|
||||||
if total < 0:
|
if total < 0:
|
||||||
total = 0.0
|
total = 0.0
|
||||||
conn.close()
|
|
||||||
return {
|
return {
|
||||||
'subtotal': round(subtotal,2),
|
'subtotal': round(subtotal, 2),
|
||||||
'item_discount': round(item_discount_total,2),
|
'item_discount': round(item_discount_total, 2),
|
||||||
'promo_code': promo_applied,
|
'promo_code': promo_applied,
|
||||||
'promo_discount': round(promo_discount,2),
|
'promo_discount': round(promo_discount, 2),
|
||||||
'total': round(total,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:
|
class App:
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
self.root = root
|
self.root = root
|
||||||
self.root.title("Cafe Totoro")
|
self.root.title("Cafe Totoro Mania")
|
||||||
self.session = None
|
self.session = None
|
||||||
self.img_cache = {}
|
self.img_cache = {}
|
||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
|
|
||||||
def setup_ui(self):
|
def setup_ui(self):
|
||||||
@ -298,18 +460,13 @@ class App:
|
|||||||
self.root.resizable(False, False)
|
self.root.resizable(False, False)
|
||||||
self.login_frame()
|
self.login_frame()
|
||||||
|
|
||||||
|
|
||||||
# windah batubara
|
|
||||||
|
|
||||||
# tampilan login dan logout
|
|
||||||
|
|
||||||
def login_frame(self):
|
def login_frame(self):
|
||||||
for w in self.root.winfo_children():
|
for w in self.root.winfo_children():
|
||||||
w.destroy()
|
w.destroy()
|
||||||
frame = ttk.Frame(self.root, padding=20)
|
frame = ttk.Frame(self.root, padding=20)
|
||||||
frame.pack(expand=True)
|
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)
|
ttk.Label(frame, text="Username:").grid(row=1, column=0, sticky='e', pady=5)
|
||||||
self.username_var = tk.StringVar()
|
self.username_var = tk.StringVar()
|
||||||
@ -335,17 +492,11 @@ class App:
|
|||||||
messagebox.showinfo("Sukses", f"Login berhasil sebagai {user['role']}")
|
messagebox.showinfo("Sukses", f"Login berhasil sebagai {user['role']}")
|
||||||
self.dashboard_frame()
|
self.dashboard_frame()
|
||||||
|
|
||||||
|
|
||||||
def logout(self):
|
def logout(self):
|
||||||
self.session = None
|
self.session = None
|
||||||
self.img_cache.clear()
|
self.img_cache.clear()
|
||||||
self.login_frame()
|
self.login_frame()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# tampilan dashboard untuk admin dah ngantuk we
|
|
||||||
|
|
||||||
def dashboard_frame(self):
|
def dashboard_frame(self):
|
||||||
for w in self.root.winfo_children():
|
for w in self.root.winfo_children():
|
||||||
w.destroy()
|
w.destroy()
|
||||||
@ -372,13 +523,6 @@ class App:
|
|||||||
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Bagian Search dan Filter
|
|
||||||
|
|
||||||
|
|
||||||
def build_menu_view_tab(self, parent):
|
def build_menu_view_tab(self, parent):
|
||||||
for w in parent.winfo_children():
|
for w in parent.winfo_children():
|
||||||
w.destroy()
|
w.destroy()
|
||||||
@ -404,14 +548,6 @@ class App:
|
|||||||
self.view_tree.pack(fill='both', expand=True)
|
self.view_tree.pack(fill='both', expand=True)
|
||||||
self.view_tree.bind("<<TreeviewSelect>>", self.on_view_select)
|
self.view_tree.bind("<<TreeviewSelect>>", self.on_view_select)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Preview Image
|
|
||||||
|
|
||||||
|
|
||||||
ttk.Label(right, text="Preview Item", font=("Arial", 12, "bold")).pack(pady=6)
|
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 = ttk.Label(right, text="Pilih menu di kiri")
|
||||||
self.preview_label.pack()
|
self.preview_label.pack()
|
||||||
@ -460,15 +596,6 @@ class App:
|
|||||||
else:
|
else:
|
||||||
self.preview_img_label.config(image='')
|
self.preview_img_label.config(image='')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# bagian menu manage khusus admin
|
|
||||||
|
|
||||||
|
|
||||||
def build_menu_manage_tab(self, parent):
|
def build_menu_manage_tab(self, parent):
|
||||||
for w in parent.winfo_children():
|
for w in parent.winfo_children():
|
||||||
w.destroy()
|
w.destroy()
|
||||||
@ -588,16 +715,6 @@ class App:
|
|||||||
if p:
|
if p:
|
||||||
var.set(p)
|
var.set(p)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Promo manage Khusus Admin
|
|
||||||
|
|
||||||
|
|
||||||
def build_promo_tab(self, parent):
|
def build_promo_tab(self, parent):
|
||||||
for w in parent.winfo_children():
|
for w in parent.winfo_children():
|
||||||
w.destroy()
|
w.destroy()
|
||||||
@ -671,12 +788,11 @@ class App:
|
|||||||
messagebox.showinfo("Sukses", "Promo ditambahkan")
|
messagebox.showinfo("Sukses", "Promo ditambahkan")
|
||||||
w.destroy()
|
w.destroy()
|
||||||
self.reload_promo_table()
|
self.reload_promo_table()
|
||||||
except sqlite3.IntegrityError:
|
except Exception as e:
|
||||||
messagebox.showerror("Error", "Kode promo sudah ada")
|
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)
|
ttk.Button(frm, text="Simpan", command=save).grid(row=4, column=1, pady=12)
|
||||||
|
|
||||||
|
|
||||||
def open_edit_promo(self):
|
def open_edit_promo(self):
|
||||||
sel = self.promo_tree.selection()
|
sel = self.promo_tree.selection()
|
||||||
if not sel:
|
if not sel:
|
||||||
@ -736,7 +852,6 @@ class App:
|
|||||||
|
|
||||||
ttk.Button(frm, text="Update", command=save).grid(row=4, column=1, pady=12)
|
ttk.Button(frm, text="Update", command=save).grid(row=4, column=1, pady=12)
|
||||||
|
|
||||||
|
|
||||||
def delete_selected_promo(self):
|
def delete_selected_promo(self):
|
||||||
sel = self.promo_tree.selection()
|
sel = self.promo_tree.selection()
|
||||||
if not sel:
|
if not sel:
|
||||||
@ -750,10 +865,16 @@ class App:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Done
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
init_conn = init_db()
|
init_db_csv()
|
||||||
init_conn.close()
|
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
app = App(root)
|
app = App(root)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
|
|
||||||
|
|||||||
5
menu.csv
Normal file
5
menu.csv
Normal file
@ -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
|
||||||
|
3
promo.csv
Normal file
3
promo.csv
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
code,type,value,min_total
|
||||||
|
PARDEDE,percent,10,0
|
||||||
|
BOTAK,fixed,5000,20000
|
||||||
|
6
users.csv
Normal file
6
users.csv
Normal file
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user