POS에서 삽입한 데이터를 GUI로 조회할 수 있는 툴
This commit is contained in:
195
lib/pos_view_gui.py
Normal file
195
lib/pos_view_gui.py
Normal file
@ -0,0 +1,195 @@
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
import customtkinter as ctk
|
||||
import tkinter.font as tkFont
|
||||
from tkinter import messagebox, ttk
|
||||
from tkcalendar import DateEntry
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy import select, func, between
|
||||
from conf import db_schema, db
|
||||
|
||||
# Windows DPI Awareness 설정 (윈도우 전용)
|
||||
if sys.platform == "win32":
|
||||
import ctypes
|
||||
try:
|
||||
ctypes.windll.shcore.SetProcessDpiAwareness(1) # SYSTEM_AWARE = 1
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
pos_table = db_schema.pos
|
||||
engine = db.engine
|
||||
|
||||
class PosViewGUI(ctk.CTk):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.title("POS 데이터 조회")
|
||||
self.geometry("900x500")
|
||||
self.configure(fg_color="#f0f0f0") # 배경색 맞춤
|
||||
|
||||
ctk.set_appearance_mode("light")
|
||||
ctk.set_default_color_theme("blue")
|
||||
|
||||
# 폰트 세팅 - NanumGothic이 없으면 Arial 대체
|
||||
try:
|
||||
self.label_font = ("NanumGothic", 13)
|
||||
except Exception:
|
||||
self.label_font = ("Arial", 13)
|
||||
|
||||
# Treeview 스타일 설정 (ttk 스타일)
|
||||
style = ttk.Style(self)
|
||||
style.theme_use('default')
|
||||
style.configure("Treeview",
|
||||
font=("NanumGothic", 12),
|
||||
rowheight=30) # 높이 조절로 글씨 깨짐 방지
|
||||
style.configure("Treeview.Heading",
|
||||
font=("NanumGothic", 13, "bold"))
|
||||
|
||||
# --- 위젯 배치 ---
|
||||
|
||||
# 날짜 범위
|
||||
ctk.CTkLabel(self, text="시작일:", anchor="w", font=self.label_font, fg_color="#f0f0f0")\
|
||||
.grid(row=0, column=0, padx=10, pady=5, sticky="e")
|
||||
self.start_date_entry = DateEntry(self, width=12, background='darkblue', foreground='white')
|
||||
self.start_date_entry.grid(row=0, column=1, padx=10, pady=5, sticky="w")
|
||||
|
||||
ctk.CTkLabel(self, text="종료일:", anchor="w", font=self.label_font, fg_color="#f0f0f0")\
|
||||
.grid(row=0, column=2, padx=10, pady=5, sticky="e")
|
||||
self.end_date_entry = DateEntry(self, width=12, background='darkblue', foreground='white')
|
||||
self.end_date_entry.grid(row=0, column=3, padx=10, pady=5, sticky="w")
|
||||
|
||||
# 대분류
|
||||
ctk.CTkLabel(self, text="대분류 :", anchor="w", font=self.label_font, fg_color="#f0f0f0")\
|
||||
.grid(row=1, column=0, padx=10, pady=5, sticky="e")
|
||||
self.ca01_combo = ctk.CTkComboBox(self, values=["전체"], width=180)
|
||||
self.ca01_combo.grid(row=1, column=1, padx=10, pady=5, sticky="w")
|
||||
self.ca01_combo.configure(command=self.on_ca01_selected)
|
||||
|
||||
# 소분류
|
||||
ctk.CTkLabel(self, text="소분류 :", anchor="w", font=self.label_font, fg_color="#f0f0f0")\
|
||||
.grid(row=1, column=2, padx=10, pady=5, sticky="e")
|
||||
self.ca03_combo = ctk.CTkComboBox(self, values=["전체"], width=180)
|
||||
self.ca03_combo.grid(row=1, column=3, padx=10, pady=5, sticky="w")
|
||||
|
||||
# 상품명
|
||||
ctk.CTkLabel(self, text="상품명 :", anchor="w", font=self.label_font, fg_color="#f0f0f0")\
|
||||
.grid(row=2, column=0, padx=10, pady=5, sticky="e")
|
||||
self.name_entry = ctk.CTkEntry(self, width=280)
|
||||
self.name_entry.grid(row=2, column=1, columnspan=3, padx=10, pady=5, sticky="w")
|
||||
|
||||
# 조회 버튼
|
||||
self.search_btn = ctk.CTkButton(self, text="조회", command=self.search,
|
||||
fg_color="#0d6efd", hover_color="#0b5ed7", text_color="white")
|
||||
self.search_btn.grid(row=3, column=0, columnspan=4, pady=10)
|
||||
|
||||
# 결과 Treeview
|
||||
self.DISPLAY_COLUMNS = ['ca01', 'ca02', 'ca03', 'name', 'qty', 'tot_amount', 'tot_discount', 'actual_amount']
|
||||
self.COLUMN_LABELS = {
|
||||
'ca01': '대분류',
|
||||
'ca02': '중분류',
|
||||
'ca03': '소분류',
|
||||
'name': '상품명',
|
||||
'qty': '수량',
|
||||
'tot_amount': '총매출액',
|
||||
'tot_discount': '총할인액',
|
||||
'actual_amount': '실매출액'
|
||||
}
|
||||
|
||||
self.tree = ttk.Treeview(self, columns=self.DISPLAY_COLUMNS, show='headings', height=15)
|
||||
for col in self.DISPLAY_COLUMNS:
|
||||
self.tree.heading(col, text=self.COLUMN_LABELS[col])
|
||||
self.tree.column(col, width=120, anchor='center')
|
||||
self.tree.grid(row=4, column=0, columnspan=4, padx=10, pady=10, sticky="nsew")
|
||||
|
||||
# 그리드 가중치 설정 (창 크기에 따라 트리뷰 확장)
|
||||
self.grid_rowconfigure(4, weight=1)
|
||||
for col_index in range(4):
|
||||
self.grid_columnconfigure(col_index, weight=1)
|
||||
|
||||
# 날짜 기본값 설정 (전날부터 7일 전까지)
|
||||
end_date = datetime.today().date() - timedelta(days=1)
|
||||
start_date = end_date - timedelta(days=6)
|
||||
self.start_date_entry.set_date(start_date)
|
||||
self.end_date_entry.set_date(end_date)
|
||||
|
||||
# 초기 대분류, 소분류 콤보박스 값 불러오기
|
||||
self.load_ca01_options()
|
||||
|
||||
def on_ca01_selected(self, value):
|
||||
# print("대분류 선택됨:", value) 디버깅용
|
||||
self.load_ca03_options()
|
||||
|
||||
def load_ca01_options(self):
|
||||
start_date = self.start_date_entry.get_date()
|
||||
end_date = self.end_date_entry.get_date()
|
||||
with engine.connect() as conn:
|
||||
stmt = select(pos_table.c.ca01).where(
|
||||
between(pos_table.c.date, start_date, end_date)
|
||||
).distinct().order_by(pos_table.c.ca01)
|
||||
result = conn.execute(stmt)
|
||||
ca01_list = [row[0] for row in result.fetchall()]
|
||||
self.ca01_combo.configure(values=['전체'] + ca01_list)
|
||||
self.ca01_combo.set('전체')
|
||||
self.load_ca03_options()
|
||||
|
||||
def load_ca03_options(self):
|
||||
start_date = self.start_date_entry.get_date()
|
||||
end_date = self.end_date_entry.get_date()
|
||||
ca01_val = self.ca01_combo.get()
|
||||
with engine.connect() as conn:
|
||||
stmt = select(pos_table.c.ca03).where(
|
||||
between(pos_table.c.date, start_date, end_date)
|
||||
)
|
||||
if ca01_val != '전체':
|
||||
stmt = stmt.where(pos_table.c.ca01 == ca01_val)
|
||||
stmt = stmt.distinct().order_by(pos_table.c.ca03)
|
||||
result = conn.execute(stmt)
|
||||
ca03_list = [row[0] for row in result.fetchall()]
|
||||
self.ca03_combo.configure(values=['전체'] + ca03_list)
|
||||
self.ca03_combo.set('전체') # 항상 기본값으로 초기화
|
||||
|
||||
def search(self):
|
||||
start_date = self.start_date_entry.get_date()
|
||||
end_date = self.end_date_entry.get_date()
|
||||
ca01_val = self.ca01_combo.get()
|
||||
ca03_val = self.ca03_combo.get()
|
||||
name_val = self.name_entry.get().strip()
|
||||
|
||||
conditions = [between(pos_table.c.date, start_date, end_date)]
|
||||
if ca01_val != '전체':
|
||||
conditions.append(pos_table.c.ca01 == ca01_val)
|
||||
if ca03_val != '전체':
|
||||
conditions.append(pos_table.c.ca03 == ca03_val)
|
||||
if name_val:
|
||||
conditions.append(pos_table.c.name.like(f"%{name_val}%"))
|
||||
|
||||
with engine.connect() as conn:
|
||||
stmt = select(
|
||||
pos_table.c.ca01,
|
||||
pos_table.c.ca02,
|
||||
pos_table.c.ca03,
|
||||
pos_table.c.name,
|
||||
func.sum(pos_table.c.qty).label("qty"),
|
||||
func.sum(pos_table.c.tot_amount).label("tot_amount"),
|
||||
func.sum(pos_table.c.tot_discount).label("tot_discount"),
|
||||
func.sum(pos_table.c.actual_amount).label("actual_amount")
|
||||
).where(*conditions).group_by(pos_table.c.barcode).order_by(pos_table.c.ca01, pos_table.c.ca03)
|
||||
|
||||
result = conn.execute(stmt).mappings().all()
|
||||
|
||||
self.tree.delete(*self.tree.get_children())
|
||||
for row in result:
|
||||
values = tuple(row[col] for col in self.DISPLAY_COLUMNS)
|
||||
self.tree.insert('', 'end', values=values)
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
import tkcalendar
|
||||
except ImportError:
|
||||
print("tkcalendar가 설치되어 있지 않습니다. 'pip install tkcalendar'로 설치해주세요.")
|
||||
sys.exit(1)
|
||||
|
||||
app = PosViewGUI()
|
||||
app.mainloop()
|
||||
Reference in New Issue
Block a user