From 0f090d508913fb2316a83d89e8ce32d548f51328 Mon Sep 17 00:00:00 2001 From: spdis Date: Sun, 27 Jul 2025 11:21:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20Simple=5FEnterprise=5FWare?= =?UTF-8?q?house=5FManagement.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Simple_Enterprise_Warehouse_Management.py | 1839 +++++++++++---------- 1 file changed, 948 insertions(+), 891 deletions(-) diff --git a/Simple_Enterprise_Warehouse_Management.py b/Simple_Enterprise_Warehouse_Management.py index 29e9528..7a12586 100644 --- a/Simple_Enterprise_Warehouse_Management.py +++ b/Simple_Enterprise_Warehouse_Management.py @@ -1,999 +1,1056 @@ -#本产品含AI量高达98% import tkinter as tk -from tkinter import ttk, messagebox +from tkinter import ttk, messagebox, simpledialog from datetime import datetime import json -import os +from inventory_manager import InventoryManager -class InventoryApp: - def __init__(self, root): - self.root = root +class InventoryUI: + def __init__(self): + self.root = tk.Tk() self.root.title("出入库管理系统") self.root.geometry("800x600") + self.root.resizable(False, False) - # 数据存储 - self.data_file = "inventory_data.json" - self.load_data() + # 初始化库存管理器 + self.manager = InventoryManager("inventory_data.json") # 当前页面状态 self.current_page = "main" self.current_product = None - self.current_main_page = 1 - self.current_detail_page = 1 - self.current_query_page = 1 - self.items_per_page = 14 - self.query_results = [] - # 窗口大小相关 - self.last_window_height = 600 + # 创建主界面 + self.create_main_page() - # 创建主框架 - self.main_frame = tk.Frame(root) - self.main_frame.pack(fill=tk.BOTH, expand=True) + def create_main_page(self): + """创建主页面 - 产品列表""" + self.clear_window() - # 绑定窗口大小变化事件 - self.root.bind('', self.on_window_resize) + # 顶部搜索栏 + search_frame = tk.Frame(self.root) + search_frame.pack(fill=tk.X, padx=10, pady=5) - # 显示主页面 - self.show_main_page() - - def load_data(self): - """加载数据""" - if os.path.exists(self.data_file): - try: - with open(self.data_file, 'r', encoding='utf-8') as f: - self.data = json.load(f) - except: - self.data = {"products": {}, "transactions": []} - else: - # 初始化空数据结构 - self.data = {"products": {}, "transactions": []} - - def save_data(self): - """保存数据""" - with open(self.data_file, 'w', encoding='utf-8') as f: - json.dump(self.data, f, ensure_ascii=False, indent=2) - - def clear_frame(self): - """清空当前框架""" - for widget in self.main_frame.winfo_children(): - widget.destroy() - - def calculate_items_per_page(self): - """根据窗口高度动态计算每页显示的条目数""" - window_height = self.root.winfo_height() - # 减去顶部搜索区域(约60px)、表头(约30px)、底部分页区域(约50px)的高度 - available_height = window_height - 140 - # 每个条目大约占用35px高度 - item_height = 35 - # 计算可显示的条目数,最少5个,最多20个 - items_count = max(5, min(20, available_height // item_height)) - return items_count - - def on_window_resize(self, event): - """窗口大小变化时的处理""" - # 只处理主窗口的大小变化事件 - if event.widget == self.root: - current_height = self.root.winfo_height() - # 如果高度变化超过50px,重新计算每页条目数 - if abs(current_height - self.last_window_height) > 50: - self.last_window_height = current_height - new_items_per_page = self.calculate_items_per_page() - if new_items_per_page != self.items_per_page: - self.items_per_page = new_items_per_page - # 重新刷新当前页面的列表 - if self.current_page == "main": - self.update_product_list() - # 更新页码标签 - total_products = len(self.data["products"]) - max_pages = (total_products + self.items_per_page - 1) // self.items_per_page - if self.current_main_page > max_pages and max_pages > 0: - self.current_main_page = max_pages - self.main_page_label.config(text=f"第 {self.current_main_page} 页 / 共 {max_pages} 页") - elif self.current_page == "detail": - self.update_detail_list() - # 更新页码标签 - product_transactions = [t for t in self.data["transactions"] if t["product"] == self.current_product] - max_pages = (len(product_transactions) + self.items_per_page - 1) // self.items_per_page - if self.current_detail_page > max_pages and max_pages > 0: - self.current_detail_page = max_pages - self.detail_page_label.config(text=f"第 {self.current_detail_page} 页 / 共 {max_pages} 页") - - def show_main_page(self): - """显示主页面""" - self.current_page = "main" - self.clear_frame() - - # 如果是首次显示主页面,保持默认的14条,否则根据窗口大小计算 - if not hasattr(self, 'main_page_initialized'): - self.main_page_initialized = True - else: - self.items_per_page = self.calculate_items_per_page() - - # 顶部搜索功能和添加产品按钮 - search_frame = tk.Frame(self.main_frame) - search_frame.pack(fill=tk.X, padx=10, pady=10) - - tk.Label(search_frame, text="搜索产品:", font=("Arial", 12)).pack(side=tk.LEFT) - self.search_var = tk.StringVar() - search_entry = tk.Entry(search_frame, textvariable=self.search_var, font=("Arial", 12)) - search_entry.pack(side=tk.LEFT, padx=(10, 5), fill=tk.X, expand=True) + tk.Label(search_frame, text="搜索产品:").pack(side=tk.LEFT) + self.search_entry = tk.Entry(search_frame, width=30) + self.search_entry.pack(side=tk.LEFT, padx=5) tk.Button(search_frame, text="搜索", command=self.search_products).pack(side=tk.LEFT, padx=2) tk.Button(search_frame, text="清空", command=self.clear_search).pack(side=tk.LEFT, padx=2) - tk.Button(search_frame, text="添加产品分类", command=self.show_add_category_page, bg="lightblue", font=("Arial", 12)).pack(side=tk.LEFT, padx=5) - tk.Button(search_frame, text="查询", command=self.show_query_page, bg="lightcyan", font=("Arial", 12)).pack(side=tk.LEFT, padx=5) + tk.Button(search_frame, text="添加产品分类", command=self.show_add_product_page).pack(side=tk.LEFT, padx=2) + tk.Button(search_frame, text="查询", command=self.show_query_page).pack(side=tk.LEFT, padx=2) - # 产品列表 - list_frame = tk.Frame(self.main_frame) - list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + # 产品列表表格 + columns = ("产品名", "当前剩余重量(kg)", "平均价格(元/kg)", "操作") + self.product_tree = ttk.Treeview(self.root, columns=columns, show="headings", height=15) - # 表头 - header_frame = tk.Frame(list_frame) - header_frame.pack(fill=tk.X) - tk.Label(header_frame, text="产品名", width=15, relief=tk.RIDGE, font=("Arial", 12, "bold")).pack(side=tk.LEFT) - tk.Label(header_frame, text="当前剩余重量(kg)", width=15, relief=tk.RIDGE, font=("Arial", 12, "bold")).pack(side=tk.LEFT) - tk.Label(header_frame, text="平均价格(元/kg)", width=15, relief=tk.RIDGE, font=("Arial", 12, "bold")).pack(side=tk.LEFT) - tk.Label(header_frame, text="操作", width=15, relief=tk.RIDGE, font=("Arial", 12, "bold")).pack(side=tk.LEFT) + for col in columns: + self.product_tree.heading(col, text=col) + self.product_tree.column(col, width=180, anchor=tk.CENTER) - # 产品条目 - self.product_list_frame = tk.Frame(list_frame) - self.product_list_frame.pack(fill=tk.BOTH, expand=True) + self.product_tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) - self.update_product_list() + # 绑定双击事件 + self.product_tree.bind("", self.on_product_select) - # 页数控制 - page_frame = tk.Frame(self.main_frame) - page_frame.pack(fill=tk.X, padx=10, pady=10) + # 底部统计信息 + stats_frame = tk.Frame(self.root) + stats_frame.pack(fill=tk.X, padx=10, pady=5) - tk.Button(page_frame, text="上一页", command=self.prev_main_page).pack(side=tk.LEFT) - total_products = len(self.data["products"]) - max_pages = max(1, (total_products + self.items_per_page - 1) // self.items_per_page) - self.main_page_label = tk.Label(page_frame, text=f"第 {self.current_main_page} 页 / 共 {max_pages} 页") - self.main_page_label.pack(side=tk.LEFT, padx=20) - tk.Button(page_frame, text="下一页", command=self.next_main_page).pack(side=tk.LEFT) - - def search_products(self): - """搜索产品""" - # 重置到第一页 - self.current_main_page = 1 - self.update_product_list() - - def clear_search(self): - """清空搜索""" - self.search_var.set("") - self.current_main_page = 1 - self.update_product_list() - - def update_product_list(self): - """更新产品列表""" - for widget in self.product_list_frame.winfo_children(): - widget.destroy() + self.stats_label = tk.Label(stats_frame, text="库存总价值: 0.00 元 库存总重: 0.0 kg") + self.stats_label.pack(side=tk.RIGHT) - # 获取搜索词并过滤产品 - search_term = self.search_var.get().strip().lower() - all_products = list(self.data["products"].items()) + # 分页控件 + page_frame = tk.Frame(self.root) + page_frame.pack(fill=tk.X, padx=10, pady=5) - if search_term: - # 模糊搜索:产品名包含搜索词 - filtered_products = [(name, data) for name, data in all_products - if search_term in name.lower()] - else: - filtered_products = all_products + tk.Button(page_frame, text="上一页").pack(side=tk.LEFT) + tk.Label(page_frame, text="第 1 页 / 共 1 页").pack(side=tk.LEFT, padx=10) + tk.Button(page_frame, text="下一页").pack(side=tk.LEFT) - # 分页处理 - start_idx = (self.current_main_page - 1) * self.items_per_page - end_idx = start_idx + self.items_per_page - page_products = filtered_products[start_idx:end_idx] + # 加载产品数据 + self.load_products() - # 显示产品列表 - for product_name, product_data in page_products: - row_frame = tk.Frame(self.product_list_frame) - row_frame.pack(fill=tk.X, pady=1) - - tk.Label(row_frame, text=product_name, width=21, relief=tk.RIDGE).pack(side=tk.LEFT) - tk.Label(row_frame, text=f"{product_data['total_weight']:.1f}", width=22, relief=tk.RIDGE).pack(side=tk.LEFT) - tk.Label(row_frame, text=f"{product_data['avg_price']:.2f}", width=21, relief=tk.RIDGE).pack(side=tk.LEFT) - tk.Button(row_frame, text="详情", width=21, - command=lambda p=product_name: self.show_detail_page(p)).pack(side=tk.LEFT) - - # 更新分页信息 - total_products = len(filtered_products) - max_pages = max(1, (total_products + self.items_per_page - 1) // self.items_per_page) - if hasattr(self, 'main_page_label') and self.main_page_label.winfo_exists(): - try: - if search_term: - self.main_page_label.config(text=f"第 {self.current_main_page} 页 / 共 {max_pages} 页 (找到 {total_products} 个结果)") - else: - self.main_page_label.config(text=f"第 {self.current_main_page} 页 / 共 {max_pages} 页") - except tk.TclError: - pass # 忽略已销毁的组件错误 - - # 如果没有搜索结果,显示提示 - if not page_products and search_term: - no_result_frame = tk.Frame(self.product_list_frame) - no_result_frame.pack(fill=tk.X, pady=20) - tk.Label(no_result_frame, text=f"未找到包含 '{search_term}' 的产品", - font=("Arial", 12), fg="gray").pack() - - def prev_main_page(self): - """上一页""" - if self.current_main_page > 1: - self.current_main_page -= 1 - self.update_product_list() - - def next_main_page(self): - """下一页""" - # 获取当前过滤后的产品数量 - search_term = self.search_var.get().strip().lower() - all_products = list(self.data["products"].items()) - - if search_term: - filtered_products = [(name, data) for name, data in all_products - if search_term in name.lower()] - else: - filtered_products = all_products - - total_products = len(filtered_products) - max_pages = max(1, (total_products + self.items_per_page - 1) // self.items_per_page) - - if self.current_main_page < max_pages: - self.current_main_page += 1 - self.update_product_list() - - def show_detail_page(self, product_name): - """显示详细条目页面""" - self.current_page = "detail" + def create_product_detail_page(self, product_name): + """创建产品详情页面""" + self.clear_window() self.current_product = product_name - self.current_detail_page = 1 - self.clear_frame() - # 如果是首次显示详情页面,保持默认的14条,否则根据窗口大小计算 - if not hasattr(self, 'detail_page_initialized'): - self.detail_page_initialized = True - else: - self.items_per_page = self.calculate_items_per_page() + # 顶部标题和按钮 + header_frame = tk.Frame(self.root) + header_frame.pack(fill=tk.X, padx=10, pady=5) - # 顶部产品名和按钮 - top_frame = tk.Frame(self.main_frame) - top_frame.pack(fill=tk.X, padx=10, pady=10) + tk.Label(header_frame, text=f"产品: {product_name}", font=("Arial", 14, "bold")).pack(side=tk.LEFT) - tk.Label(top_frame, text=f"产品: {product_name}", font=("Arial", 16, "bold")).pack(side=tk.LEFT) - - button_frame = tk.Frame(top_frame) + button_frame = tk.Frame(header_frame) button_frame.pack(side=tk.RIGHT) - tk.Button(button_frame, text="入库", command=self.show_inbound_page, bg="lightgreen").pack(side=tk.LEFT, padx=5) - tk.Button(button_frame, text="出库", command=self.show_outbound_page, bg="lightcoral").pack(side=tk.LEFT, padx=5) - tk.Button(button_frame, text="删除产品", command=self.delete_product_category, bg="darkred", fg="white").pack(side=tk.LEFT, padx=5) - tk.Button(button_frame, text="返回主页", command=self.show_main_page).pack(side=tk.LEFT, padx=5) - + tk.Button(button_frame, text="入库", bg="lightgreen", command=self.show_inbound_dialog).pack(side=tk.LEFT, padx=2) + tk.Button(button_frame, text="出库", bg="lightcoral", command=self.show_outbound_dialog).pack(side=tk.LEFT, padx=2) + tk.Button(button_frame, text="删除产品", bg="red", fg="white", command=self.delete_product).pack(side=tk.LEFT, padx=2) + tk.Button(button_frame, text="返回主页", command=self.create_main_page).pack(side=tk.LEFT, padx=2) - # 详细条目列表 - detail_frame = tk.Frame(self.main_frame) - detail_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + # 交易记录表格 + columns = ("时间", "类型", "重量(kg)", "价格(元/kg)", "总价(元)", "备注", "操作") + self.transaction_tree = ttk.Treeview(self.root, columns=columns, show="headings", height=18) - # 表头 - header_frame = tk.Frame(detail_frame) - header_frame.pack(fill=tk.X) - tk.Label(header_frame, text="时间", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) - tk.Label(header_frame, text="类型", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) - tk.Label(header_frame, text="重量(kg)", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) - tk.Label(header_frame, text="价格(元/kg)", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) - tk.Label(header_frame, text="总价(元)", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) - tk.Label(header_frame, text="备注", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) - tk.Label(header_frame, text="操作", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) + for col in columns: + self.transaction_tree.heading(col, text=col) + if col == "时间": + self.transaction_tree.column(col, width=100) + elif col == "类型": + self.transaction_tree.column(col, width=80) + elif col == "操作": + self.transaction_tree.column(col, width=80) + else: + self.transaction_tree.column(col, width=100) - # 交易记录 - self.detail_list_frame = tk.Frame(detail_frame) - self.detail_list_frame.pack(fill=tk.BOTH, expand=True) + self.transaction_tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) - self.update_detail_list() + # 绑定删除事件 + self.transaction_tree.bind("", self.delete_transaction) - # 页数控制 - page_frame = tk.Frame(self.main_frame) - page_frame.pack(fill=tk.X, padx=10, pady=10) + # 加载交易记录 + self.load_transactions(product_name) - tk.Button(page_frame, text="上一页", command=self.prev_detail_page).pack(side=tk.LEFT) - product_transactions = [t for t in self.data["transactions"] if t["product"] == self.current_product] - max_pages = max(1, (len(product_transactions) + self.items_per_page - 1) // self.items_per_page) - self.detail_page_label = tk.Label(page_frame, text=f"第 {self.current_detail_page} 页 / 共 {max_pages} 页") - self.detail_page_label.pack(side=tk.LEFT, padx=20) - tk.Button(page_frame, text="下一页", command=self.next_detail_page).pack(side=tk.LEFT) - - def update_detail_list(self): - """更新详细条目列表""" - for widget in self.detail_list_frame.winfo_children(): - widget.destroy() - - # 筛选当前产品的交易记录 - product_transactions = [t for t in self.data["transactions"] if t["product"] == self.current_product] - product_transactions.sort(key=lambda x: x["time"], reverse=True) - - start_idx = (self.current_detail_page - 1) * self.items_per_page - end_idx = start_idx + self.items_per_page - page_transactions = product_transactions[start_idx:end_idx] - - for i, transaction in enumerate(page_transactions): - row_frame = tk.Frame(self.detail_list_frame) - row_frame.pack(fill=tk.X, pady=1) - - total_price = transaction['weight'] * transaction['price'] - - tk.Label(row_frame, text=transaction["time"], width=15, relief=tk.RIDGE).pack(side=tk.LEFT) - color = "lightgreen" if transaction["type"] == "入库" else "lightcoral" - tk.Label(row_frame, text=transaction["type"], width=15, relief=tk.RIDGE, bg=color).pack(side=tk.LEFT) - tk.Label(row_frame, text=f"{transaction['weight']:.1f}", width=14, relief=tk.RIDGE).pack(side=tk.LEFT) - tk.Label(row_frame, text=f"{transaction['price']:.2f}", width=15, relief=tk.RIDGE).pack(side=tk.LEFT) - tk.Label(row_frame, text=f"{total_price:.2f}", width=15, relief=tk.RIDGE).pack(side=tk.LEFT) - tk.Label(row_frame, text=transaction["note"], width=14, relief=tk.RIDGE).pack(side=tk.LEFT) - tk.Button(row_frame, text="删除", width=15, bg="red", fg="white", - command=lambda t=transaction: self.delete_transaction(t)).pack(side=tk.LEFT) - - def prev_detail_page(self): - """上一页""" - if self.current_detail_page > 1: - self.current_detail_page -= 1 - self.update_detail_list() - product_transactions = [t for t in self.data["transactions"] if t["product"] == self.current_product] - max_pages = max(1, (len(product_transactions) + self.items_per_page - 1) // self.items_per_page) - self.detail_page_label.config(text=f"第 {self.current_detail_page} 页 / 共 {max_pages} 页") - - def next_detail_page(self): - """详情页下一页""" - product_transactions = [t for t in self.data["transactions"] if t["product"] == self.current_product] - max_pages = (len(product_transactions) + self.items_per_page - 1) // self.items_per_page - if self.current_detail_page < max_pages: - self.current_detail_page += 1 - self.update_detail_list() - self.detail_page_label.config(text=f"第 {self.current_detail_page} 页 / 共 {max_pages} 页") - - def show_inbound_page(self): - """显示入库页面""" - self.current_page = "inbound" - self.clear_frame() + def create_add_product_page(self): + """创建添加产品页面""" + self.clear_window() # 标题 - title_frame = tk.Frame(self.main_frame) - title_frame.pack(fill=tk.X, padx=10, pady=10) - tk.Label(title_frame, text=f"入库 - {self.current_product}", font=("Arial", 16, "bold")).pack(side=tk.LEFT) - tk.Button(title_frame, text="返回详情", command=lambda: self.show_detail_page(self.current_product)).pack(side=tk.RIGHT) + tk.Label(self.root, text="添加产品大类", font=("Arial", 16, "bold")).pack(pady=20) - # 输入表单 - form_frame = tk.Frame(self.main_frame) - form_frame.pack(fill=tk.X, padx=50, pady=50) + # 表单 + form_frame = tk.Frame(self.root) + form_frame.pack(pady=50) - # 入库重量 - tk.Label(form_frame, text="入库重量(kg):", font=("Arial", 12)).grid(row=0, column=0, sticky="w", pady=10) - self.inbound_weight_var = tk.StringVar() - tk.Entry(form_frame, textvariable=self.inbound_weight_var, font=("Arial", 12), width=20).grid(row=0, column=1, padx=10, pady=10) + tk.Label(form_frame, text="产品名称:").grid(row=0, column=0, sticky=tk.W, padx=10, pady=10) + self.product_name_entry = tk.Entry(form_frame, width=30) + self.product_name_entry.grid(row=0, column=1, padx=10, pady=10) - # 入库价格 - tk.Label(form_frame, text="入库价格(元/kg):", font=("Arial", 12)).grid(row=1, column=0, sticky="w", pady=10) - self.inbound_price_var = tk.StringVar() - tk.Entry(form_frame, textvariable=self.inbound_price_var, font=("Arial", 12), width=20).grid(row=1, column=1, padx=10, pady=10) + tk.Label(form_frame, text="产品描述:").grid(row=1, column=0, sticky=tk.W, padx=10, pady=10) + self.product_desc_entry = tk.Entry(form_frame, width=30) + self.product_desc_entry.grid(row=1, column=1, padx=10, pady=10) - # 入库日期 - tk.Label(form_frame, text="入库日期:", font=("Arial", 12)).grid(row=2, column=0, sticky="w", pady=10) - self.inbound_date_var = tk.StringVar(value=datetime.now().strftime("%Y-%m-%d")) - date_entry = tk.Entry(form_frame, textvariable=self.inbound_date_var, font=("Arial", 12), width=20) - date_entry.grid(row=2, column=1, padx=10, pady=10) - tk.Label(form_frame, text="(格式: 2024-01-01)", font=("Arial", 8), fg="gray").grid(row=2, column=2, sticky="w", padx=5) + # 按钮 + button_frame = tk.Frame(self.root) + button_frame.pack(pady=30) - # 备注 - tk.Label(form_frame, text="备注:", font=("Arial", 12)).grid(row=3, column=0, sticky="w", pady=10) - self.inbound_note_var = tk.StringVar() - tk.Entry(form_frame, textvariable=self.inbound_note_var, font=("Arial", 12), width=20).grid(row=3, column=1, padx=10, pady=10) + tk.Button(button_frame, text="确认添加", bg="lightblue", command=self.add_product).pack(side=tk.LEFT, padx=10) + tk.Button(button_frame, text="取消", command=self.create_main_page).pack(side=tk.LEFT, padx=10) - # 提交按钮 - button_frame = tk.Frame(form_frame) - button_frame.grid(row=4, column=0, columnspan=2, pady=20) - tk.Button(button_frame, text="确认入库", command=self.process_inbound, bg="lightgreen", font=("Arial", 12)).pack(side=tk.LEFT, padx=10) - tk.Button(button_frame, text="取消", command=lambda: self.show_detail_page(self.current_product), font=("Arial", 12)).pack(side=tk.LEFT, padx=10) - - def show_outbound_page(self): - """显示出库页面""" - self.current_page = "outbound" - self.clear_frame() - - # 标题 - title_frame = tk.Frame(self.main_frame) - title_frame.pack(fill=tk.X, padx=10, pady=10) - tk.Label(title_frame, text=f"出库 - {self.current_product}", font=("Arial", 16, "bold")).pack(side=tk.LEFT) - tk.Button(title_frame, text="返回详情", command=lambda: self.show_detail_page(self.current_product)).pack(side=tk.RIGHT) - - # 输入表单 - form_frame = tk.Frame(self.main_frame) - form_frame.pack(fill=tk.X, padx=50, pady=50) - - # 出库重量 - tk.Label(form_frame, text="出库重量(kg):", font=("Arial", 12)).grid(row=0, column=0, sticky="w", pady=10) - self.outbound_weight_var = tk.StringVar() - tk.Entry(form_frame, textvariable=self.outbound_weight_var, font=("Arial", 12), width=20).grid(row=0, column=1, padx=10, pady=10) - - # 出库价格(自动计算) - tk.Label(form_frame, text="出库价格(元/kg):", font=("Arial", 12)).grid(row=1, column=0, sticky="w", pady=10) - current_price = self.data["products"][self.current_product]["avg_price"] - self.outbound_price_var = tk.StringVar(value=str(current_price)) - price_entry = tk.Entry(form_frame, textvariable=self.outbound_price_var, font=("Arial", 12), width=20, state="readonly") - price_entry.grid(row=1, column=1, padx=10, pady=10) - - # 出库日期 - tk.Label(form_frame, text="出库日期:", font=("Arial", 12)).grid(row=2, column=0, sticky="w", pady=10) - self.outbound_date_var = tk.StringVar(value=datetime.now().strftime("%Y-%m-%d")) - date_entry = tk.Entry(form_frame, textvariable=self.outbound_date_var, font=("Arial", 12), width=20) - date_entry.grid(row=2, column=1, padx=10, pady=10) - tk.Label(form_frame, text="(格式: 2024-01-01)", font=("Arial", 8), fg="gray").grid(row=2, column=2, sticky="w", padx=5) - - # 备注 - tk.Label(form_frame, text="备注:", font=("Arial", 12)).grid(row=3, column=0, sticky="w", pady=10) - self.outbound_note_var = tk.StringVar() - tk.Entry(form_frame, textvariable=self.outbound_note_var, font=("Arial", 12), width=20).grid(row=3, column=1, padx=10, pady=10) - - # 提交按钮 - button_frame = tk.Frame(form_frame) - button_frame.grid(row=4, column=0, columnspan=2, pady=20) - tk.Button(button_frame, text="确认出库", command=self.process_outbound, bg="lightcoral", font=("Arial", 12)).pack(side=tk.LEFT, padx=10) - tk.Button(button_frame, text="取消", command=lambda: self.show_detail_page(self.current_product), font=("Arial", 12)).pack(side=tk.LEFT, padx=10) - - def process_inbound(self): - """处理入库""" - try: - weight = float(self.inbound_weight_var.get()) - price = float(self.inbound_price_var.get()) - note = self.inbound_note_var.get().strip() - date_str = self.inbound_date_var.get().strip() - - if weight <= 0 or price <= 0: - messagebox.showerror("错误", "重量和价格必须大于0") - return - - # 验证日期格式 - try: - datetime.strptime(date_str, "%Y-%m-%d") - except ValueError: - messagebox.showerror("错误", "请输入正确的日期格式 (YYYY-MM-DD)") - return - - # 更新产品数据 - if self.current_product not in self.data["products"]: - self.data["products"][self.current_product] = {"total_weight": 0, "avg_price": 0} - - product_data = self.data["products"][self.current_product] - old_total_weight = product_data["total_weight"] - old_avg_price = product_data["avg_price"] - - # 计算新的平均价格 - new_total_weight = old_total_weight + weight - new_avg_price = (old_total_weight * old_avg_price + weight * price) / new_total_weight - - product_data["total_weight"] = new_total_weight - product_data["avg_price"] = new_avg_price - - # 添加交易记录 - transaction = { - "product": self.current_product, - "type": "入库", - "weight": weight, - "price": price, - "note": note, - "time": date_str - } - self.data["transactions"].append(transaction) - - # 保存数据 - self.save_data() - - messagebox.showinfo("成功", "入库成功!") - self.show_detail_page(self.current_product) - - except ValueError: - messagebox.showerror("错误", "请输入有效的数字") - - def process_outbound(self): - """处理出库""" - try: - weight = float(self.outbound_weight_var.get()) - price = float(self.outbound_price_var.get()) - note = self.outbound_note_var.get().strip() - date_str = self.outbound_date_var.get().strip() - - if weight <= 0: - messagebox.showerror("错误", "重量必须大于0") - return - - # 验证日期格式 - try: - datetime.strptime(date_str, "%Y-%m-%d") - except ValueError: - messagebox.showerror("错误", "请输入正确的日期格式 (YYYY-MM-DD)") - return - - # 检查库存是否足够 - current_weight = self.data["products"][self.current_product]["total_weight"] - if weight > current_weight: - messagebox.showerror("错误", f"库存不足!当前库存: {current_weight:.1f}kg") - return - - # 更新产品数据 - self.data["products"][self.current_product]["total_weight"] -= weight - - # 添加交易记录 - transaction = { - "product": self.current_product, - "type": "出库", - "weight": weight, - "price": price, - "note": note, - "time": date_str - } - self.data["transactions"].append(transaction) - - # 保存数据 - self.save_data() - - messagebox.showinfo("成功", "出库成功!") - self.show_detail_page(self.current_product) - - except ValueError: - messagebox.showerror("错误", "请输入有效的数字") - - def show_add_category_page(self): - """显示添加产品大类页面""" - self.current_page = "add_category" - self.clear_frame() - - # 标题 - title_frame = tk.Frame(self.main_frame) - title_frame.pack(fill=tk.X, padx=10, pady=10) - tk.Label(title_frame, text="添加产品大类", font=("Arial", 16, "bold")).pack(side=tk.LEFT) - tk.Button(title_frame, text="返回主页", command=self.show_main_page).pack(side=tk.RIGHT) - - # 输入表单 - form_frame = tk.Frame(self.main_frame) - form_frame.pack(fill=tk.X, padx=50, pady=50) - - # 产品名称 - tk.Label(form_frame, text="产品名称:", font=("Arial", 12)).grid(row=0, column=0, sticky="w", pady=10) - self.category_name_var = tk.StringVar() - tk.Entry(form_frame, textvariable=self.category_name_var, font=("Arial", 12), width=30).grid(row=0, column=1, padx=10, pady=10) - - # 初始重量 - tk.Label(form_frame, text="初始重量(kg):", font=("Arial", 12)).grid(row=1, column=0, sticky="w", pady=10) - self.category_weight_var = tk.StringVar(value="0") - tk.Entry(form_frame, textvariable=self.category_weight_var, font=("Arial", 12), width=30).grid(row=1, column=1, padx=10, pady=10) - - # 初始价格 - tk.Label(form_frame, text="初始价格(元/kg):", font=("Arial", 12)).grid(row=2, column=0, sticky="w", pady=10) - self.category_price_var = tk.StringVar(value="0") - tk.Entry(form_frame, textvariable=self.category_price_var, font=("Arial", 12), width=30).grid(row=2, column=1, padx=10, pady=10) - - # 产品描述 - tk.Label(form_frame, text="产品描述:", font=("Arial", 12)).grid(row=3, column=0, sticky="w", pady=10) - self.category_desc_var = tk.StringVar() - tk.Entry(form_frame, textvariable=self.category_desc_var, font=("Arial", 12), width=30).grid(row=3, column=1, padx=10, pady=10) - - # 提交按钮 - button_frame = tk.Frame(form_frame) - button_frame.grid(row=4, column=0, columnspan=2, pady=20) - tk.Button(button_frame, text="确认添加", command=self.process_add_category, bg="lightblue", font=("Arial", 12)).pack(side=tk.LEFT, padx=10) - tk.Button(button_frame, text="取消", command=self.show_main_page, font=("Arial", 12)).pack(side=tk.LEFT, padx=10) + # 返回按钮 + tk.Button(self.root, text="返回主页", command=self.create_main_page).pack(side=tk.TOP, anchor=tk.NE, padx=10, pady=10) # 说明文字 - info_frame = tk.Frame(self.main_frame) - info_frame.pack(fill=tk.X, padx=50, pady=10) - info_text = "说明:\n• 产品名称不能为空且不能与现有产品重复\n• 初始重量和价格可以设为0,后续通过入库操作添加\n• 产品描述为可选项" - tk.Label(info_frame, text=info_text, font=("Arial", 10), justify=tk.LEFT, fg="gray").pack(anchor="w") - - def process_add_category(self): - """处理添加产品大类""" - try: - name = self.category_name_var.get().strip() - weight = float(self.category_weight_var.get()) - price = float(self.category_price_var.get()) - description = self.category_desc_var.get().strip() - - # 验证输入 - if not name: - messagebox.showerror("错误", "产品名称不能为空") - return - - if name in self.data["products"]: - messagebox.showerror("错误", f"产品 '{name}' 已存在,请使用其他名称") - return - - if weight < 0 or price < 0: - messagebox.showerror("错误", "重量和价格不能为负数") - return - - # 添加新产品 - self.data["products"][name] = { - "total_weight": weight, - "avg_price": price - } - - # 如果有初始重量和价格,添加初始入库记录 - if weight > 0 and price > 0: - transaction = { - "product": name, - "type": "入库", - "weight": weight, - "price": price, - "note": f"初始库存 - {description}" if description else "初始库存", - "time": datetime.now().strftime("%Y-%m-%d %H:%M") - } - self.data["transactions"].append(transaction) - - # 保存数据 - self.save_data() - - messagebox.showinfo("成功", f"产品大类 '{name}' 添加成功!") - self.show_main_page() - - except ValueError: - messagebox.showerror("错误", "请输入有效的数字") - - def show_query_page(self): - """显示查询页面""" - self.current_page = "query" - self.current_query_page = 1 - self.clear_frame() + info_frame = tk.Frame(self.root) + info_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=20, pady=20) + + tk.Label(info_frame, text="说明:", font=("Arial", 10, "bold")).pack(anchor=tk.W) + tk.Label(info_frame, text="• 产品名称不能为空且不能与现有产品重复").pack(anchor=tk.W) + tk.Label(info_frame, text="• 产品描述为可选项").pack(anchor=tk.W) + tk.Label(info_frame, text="• 添加产品后,可通过入库操作添加库存").pack(anchor=tk.W) + + def create_query_page(self): + """创建订单查询页面""" + self.clear_window() # 标题 - title_frame = tk.Frame(self.main_frame) - title_frame.pack(fill=tk.X, padx=10, pady=10) - tk.Label(title_frame, text="订单查询", font=("Arial", 16, "bold")).pack(side=tk.LEFT) - tk.Button(title_frame, text="返回主页", command=self.show_main_page).pack(side=tk.RIGHT) + tk.Label(self.root, text="订单查询", font=("Arial", 16, "bold")).pack(pady=10) # 查询条件 - query_frame = tk.Frame(self.main_frame) + query_frame = tk.Frame(self.root) query_frame.pack(fill=tk.X, padx=10, pady=10) - # 第一行:产品名称和交易类型 - row1_frame = tk.Frame(query_frame) - row1_frame.pack(fill=tk.X, pady=5) + # 第一行 + row1 = tk.Frame(query_frame) + row1.pack(fill=tk.X, pady=5) - tk.Label(row1_frame, text="产品名称:", font=("Arial", 10)).pack(side=tk.LEFT) - self.query_product_var = tk.StringVar() - tk.Entry(row1_frame, textvariable=self.query_product_var, font=("Arial", 10), width=15).pack(side=tk.LEFT, padx=(5, 20)) + tk.Label(row1, text="产品名称:").pack(side=tk.LEFT) + self.query_product_entry = tk.Entry(row1, width=15) + self.query_product_entry.pack(side=tk.LEFT, padx=5) - tk.Label(row1_frame, text="交易类型:", font=("Arial", 10)).pack(side=tk.LEFT) - self.query_type_var = tk.StringVar() - type_combo = tk.OptionMenu(row1_frame, self.query_type_var, "全部", "入库", "出库") - type_combo.config(font=("Arial", 10)) - self.query_type_var.set("全部") + tk.Label(row1, text="交易类型:").pack(side=tk.LEFT, padx=(20,5)) + self.query_type_var = tk.StringVar(value="全部") + type_combo = ttk.Combobox(row1, textvariable=self.query_type_var, values=["全部", "入库", "出库"], width=10) type_combo.pack(side=tk.LEFT, padx=5) - # 第二行:时间范围 - row2_frame = tk.Frame(query_frame) - row2_frame.pack(fill=tk.X, pady=5) + # 第二行 + row2 = tk.Frame(query_frame) + row2.pack(fill=tk.X, pady=5) - tk.Label(row2_frame, text="开始时间:", font=("Arial", 10)).pack(side=tk.LEFT) - self.query_start_date_var = tk.StringVar() - tk.Entry(row2_frame, textvariable=self.query_start_date_var, font=("Arial", 10), width=12).pack(side=tk.LEFT, padx=(5, 10)) - tk.Label(row2_frame, text="(格式: 2024-01-01)", font=("Arial", 8), fg="gray").pack(side=tk.LEFT, padx=(0, 20)) + tk.Label(row2, text="开始时间:").pack(side=tk.LEFT) + self.start_date_entry = tk.Entry(row2, width=12) + self.start_date_entry.pack(side=tk.LEFT, padx=5) + self.start_date_entry.insert(0, "2025-06-26") + tk.Label(row2, text="(格式:2025-7-1)").pack(side=tk.LEFT) - tk.Label(row2_frame, text="结束时间:", font=("Arial", 10)).pack(side=tk.LEFT) - self.query_end_date_var = tk.StringVar() - tk.Entry(row2_frame, textvariable=self.query_end_date_var, font=("Arial", 10), width=12).pack(side=tk.LEFT, padx=(5, 10)) - tk.Label(row2_frame, text="(格式: 2024-12-31)", font=("Arial", 8), fg="gray").pack(side=tk.LEFT) + tk.Label(row2, text="结束时间:").pack(side=tk.LEFT, padx=(20,5)) + self.end_date_entry = tk.Entry(row2, width=12) + self.end_date_entry.pack(side=tk.LEFT, padx=5) + self.end_date_entry.insert(0, "2025-07-26") + tk.Label(row2, text="(格式:2025-12-31)").pack(side=tk.LEFT) - # 第三行:重量和价格范围 - row3_frame = tk.Frame(query_frame) - row3_frame.pack(fill=tk.X, pady=5) + # 第三行 + row3 = tk.Frame(query_frame) + row3.pack(fill=tk.X, pady=5) - tk.Label(row3_frame, text="最小重量:", font=("Arial", 10)).pack(side=tk.LEFT) - self.query_min_weight_var = tk.StringVar() - tk.Entry(row3_frame, textvariable=self.query_min_weight_var, font=("Arial", 10), width=10).pack(side=tk.LEFT, padx=(5, 20)) + tk.Label(row3, text="最小重量:").pack(side=tk.LEFT) + self.min_weight_entry = tk.Entry(row3, width=12) + self.min_weight_entry.pack(side=tk.LEFT, padx=5) - tk.Label(row3_frame, text="最大重量:", font=("Arial", 10)).pack(side=tk.LEFT) - self.query_max_weight_var = tk.StringVar() - tk.Entry(row3_frame, textvariable=self.query_max_weight_var, font=("Arial", 10), width=10).pack(side=tk.LEFT, padx=(5, 20)) + tk.Label(row3, text="最大重量:").pack(side=tk.LEFT, padx=(20,5)) + self.max_weight_entry = tk.Entry(row3, width=12) + self.max_weight_entry.pack(side=tk.LEFT, padx=5) - tk.Label(row3_frame, text="备注关键词:", font=("Arial", 10)).pack(side=tk.LEFT) - self.query_note_var = tk.StringVar() - tk.Entry(row3_frame, textvariable=self.query_note_var, font=("Arial", 10), width=15).pack(side=tk.LEFT, padx=5) + tk.Label(row3, text="备注关键词:").pack(side=tk.LEFT, padx=(20,5)) + self.note_keyword_entry = tk.Entry(row3, width=15) + self.note_keyword_entry.pack(side=tk.LEFT, padx=5) # 查询按钮 button_frame = tk.Frame(query_frame) button_frame.pack(fill=tk.X, pady=10) - tk.Button(button_frame, text="查询", command=self.execute_query, bg="lightgreen", font=("Arial", 12)).pack(side=tk.LEFT, padx=5) - tk.Button(button_frame, text="清空条件", command=self.clear_query_conditions, font=("Arial", 12)).pack(side=tk.LEFT, padx=5) + tk.Button(button_frame, text="查询", bg="lightgreen", command=self.execute_query).pack(side=tk.LEFT, padx=5) + tk.Button(button_frame, text="清空条件", command=self.clear_query_conditions).pack(side=tk.LEFT, padx=5) + tk.Button(button_frame, text="打印查询结果", bg="lightblue", command=self.print_query_results).pack(side=tk.LEFT, padx=5) - # 查询结果列表 - result_frame = tk.Frame(self.main_frame) - result_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + # 快速查询按钮 + quick_frame = tk.Frame(button_frame) + quick_frame.pack(side=tk.RIGHT) - # 表头 - header_frame = tk.Frame(result_frame) - header_frame.pack(fill=tk.X) - tk.Label(header_frame, text="时间", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) - tk.Label(header_frame, text="产品名", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) - tk.Label(header_frame, text="类型", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) - tk.Label(header_frame, text="重量(kg)", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) - tk.Label(header_frame, text="价格(元/kg)", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) - tk.Label(header_frame, text="总价(元)", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) - tk.Label(header_frame, text="备注", width=13, relief=tk.RIDGE, font=("Arial", 10, "bold")).pack(side=tk.LEFT) + tk.Label(quick_frame, text="快速查询:").pack(side=tk.LEFT, padx=(20,5)) + tk.Button(quick_frame, text="今日", command=lambda: self.quick_query("today"), bg="lightyellow").pack(side=tk.LEFT, padx=2) + tk.Button(quick_frame, text="本周", command=lambda: self.quick_query("week"), bg="lightyellow").pack(side=tk.LEFT, padx=2) + tk.Button(quick_frame, text="本月", command=lambda: self.quick_query("month"), bg="lightyellow").pack(side=tk.LEFT, padx=2) + tk.Button(quick_frame, text="全部", command=lambda: self.quick_query("all"), bg="lightyellow").pack(side=tk.LEFT, padx=2) - # 查询结果 - self.query_result_frame = tk.Frame(result_frame) - self.query_result_frame.pack(fill=tk.BOTH, expand=True) + # 查询结果表格 + columns = ("时间", "产品名", "类型", "重量(kg)", "价格(元/kg)", "总价(元)", "备注") + self.query_tree = ttk.Treeview(self.root, columns=columns, show="headings", height=12) - # 分页控制 - self.query_page_frame = tk.Frame(self.main_frame) - self.query_page_frame.pack(fill=tk.X, padx=10, pady=10) + for col in columns: + self.query_tree.heading(col, text=col) + self.query_tree.column(col, width=100, anchor=tk.CENTER) - tk.Button(self.query_page_frame, text="上一页", command=self.prev_query_page).pack(side=tk.LEFT) - self.query_page_label = tk.Label(self.query_page_frame, text="第 1 页 / 共 1 页") - self.query_page_label.pack(side=tk.LEFT, padx=20) - tk.Button(self.query_page_frame, text="下一页", command=self.next_query_page).pack(side=tk.LEFT) + self.query_tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) - # 设置默认查询条件:最近一个月 - from datetime import datetime, timedelta - today = datetime.now() - one_month_ago = today - timedelta(days=30) - self.query_start_date_var.set(one_month_ago.strftime("%Y-%m-%d")) - self.query_end_date_var.set(today.strftime("%Y-%m-%d")) + # 底部统计和分页 + bottom_frame = tk.Frame(self.root) + bottom_frame.pack(fill=tk.X, padx=10, pady=5) - # 初始显示最近一个月的交易记录 - self.execute_query() + # 分页 + page_frame = tk.Frame(bottom_frame) + page_frame.pack(side=tk.LEFT) + + # 初始化分页变量 + self.current_page = 1 + self.page_size = 20 # 每页显示20条记录 + self.total_records = 0 + self.filtered_transactions = [] # 存储过滤后的交易记录 + + self.prev_button = tk.Button(page_frame, text="上一页", command=self.prev_page) + self.prev_button.pack(side=tk.LEFT) + + self.page_info_label = tk.Label(page_frame, text="第 1 页 / 共 1 页 (共 0 条记录)") + self.page_info_label.pack(side=tk.LEFT, padx=10) + + self.next_button = tk.Button(page_frame, text="下一页", command=self.next_page) + self.next_button.pack(side=tk.LEFT) + + # 每页显示数量选择 + tk.Label(page_frame, text="每页显示:").pack(side=tk.LEFT, padx=(20,5)) + self.page_size_var = tk.StringVar(value="20") + page_size_combo = ttk.Combobox(page_frame, textvariable=self.page_size_var, + values=["10", "20", "50", "100"], width=8) + page_size_combo.pack(side=tk.LEFT, padx=5) + page_size_combo.bind("<>", self.on_page_size_change) + + # 统计信息 + self.query_stats_label = tk.Label(bottom_frame, text="总重量: 360.0kg 总价: 11680.00元") + self.query_stats_label.pack(side=tk.RIGHT) + + # 返回按钮 + tk.Button(self.root, text="返回主页", command=self.create_main_page).pack(side=tk.TOP, anchor=tk.NE, padx=10, pady=5) + + def clear_window(self): + """清空窗口内容""" + for widget in self.root.winfo_children(): + widget.destroy() + + def load_products(self): + """加载产品列表""" + try: + with open("inventory_data.json", "r", encoding="utf-8") as f: + data = json.load(f) + + # 清空现有数据 + for item in self.product_tree.get_children(): + self.product_tree.delete(item) + + total_value = 0 + total_weight = 0 + + for product_name, product_data in data["products"].items(): + weight = float(product_data["total_weight"]) + price = float(product_data["avg_price"]) + value = weight * price + + total_weight += weight + total_value += value + + self.product_tree.insert("", tk.END, values=( + product_name, + f"{weight:.8f}", + f"{price:.8f}", + "详情" + )) + + # 更新统计信息 + self.stats_label.config(text=f"库存总价值: {total_value:.2f} 元 库存总重: {total_weight:.2f} kg") + + except Exception as e: + messagebox.showerror("错误", f"加载产品数据失败: {str(e)}") + + def load_transactions(self, product_name): + """加载指定产品的交易记录""" + try: + with open("inventory_data.json", "r", encoding="utf-8") as f: + data = json.load(f) + + # 清空现有数据 + for item in self.transaction_tree.get_children(): + self.transaction_tree.delete(item) + + # 筛选该产品的交易记录 + for transaction in data["transactions"]: + if transaction.get("is_snapshot"): + continue + + if transaction["product"] == product_name: + txn_type = transaction["type"] + weight = float(transaction["weight"]) + price = float(transaction["price"]) + total_price = weight * price + + # 设置颜色 + tag = "inbound" if txn_type == "入库" else "outbound" + + item = self.transaction_tree.insert("", tk.END, values=( + transaction["time"], + txn_type, + f"{weight:.2f}", + f"{price:.2f}", + f"{total_price:.2f}", + transaction.get("note", ""), + "删除" + ), tags=(tag,)) + + # 设置标签颜色 + self.transaction_tree.tag_configure("inbound", background="lightgreen") + self.transaction_tree.tag_configure("outbound", background="lightcoral") + + except Exception as e: + messagebox.showerror("错误", f"加载交易记录失败: {str(e)}") + + def on_product_select(self, event): + """产品选择事件""" + selection = self.product_tree.selection() + if selection: + item = self.product_tree.item(selection[0]) + product_name = item["values"][0] + self.create_product_detail_page(product_name) + + def show_add_product_page(self): + """显示添加产品页面""" + self.create_add_product_page() + + def show_query_page(self): + """显示查询页面""" + self.create_query_page() + + def show_inbound_dialog(self): + """显示入库对话框""" + dialog = tk.Toplevel(self.root) + dialog.title("入库") + dialog.geometry("350x350") + dialog.resizable(False, False) + + # 居中显示 + dialog.transient(self.root) + dialog.grab_set() + + # 计算居中位置 + x = (dialog.winfo_screenwidth() // 2) - (350 // 2) + y = (dialog.winfo_screenheight() // 2) - (350 // 2) + dialog.geometry(f"350x350+{x}+{y}") + + tk.Label(dialog, text=f"产品: {self.current_product}", font=("Arial", 12, "bold")).pack(pady=15) + + tk.Label(dialog, text="重量(kg):").pack() + weight_entry = tk.Entry(dialog, width=20) + weight_entry.pack(pady=5) + + tk.Label(dialog, text="单价(元/kg):").pack() + price_entry = tk.Entry(dialog, width=20) + price_entry.pack(pady=5) + + tk.Label(dialog, text="日期(YYYY-MM-DD):").pack() + date_entry = tk.Entry(dialog, width=20) + date_entry.insert(0, datetime.now().strftime("%Y-%m-%d")) # 默认今天日期 + date_entry.pack(pady=5) + + tk.Label(dialog, text="备注:").pack() + note_entry = tk.Entry(dialog, width=20) + note_entry.pack(pady=5) + + # 设置回车键切换输入框 + def focus_next_entry(event, next_entry): + next_entry.focus_set() + return "break" + + def confirm_on_enter(event): + confirm_inbound() + return "break" + + weight_entry.bind("", lambda e: focus_next_entry(e, price_entry)) + price_entry.bind("", lambda e: focus_next_entry(e, date_entry)) + date_entry.bind("", lambda e: focus_next_entry(e, note_entry)) + note_entry.bind("", confirm_on_enter) + + # 默认焦点在重量输入框 + weight_entry.focus_set() + + def confirm_inbound(): + try: + weight = float(weight_entry.get()) + price = float(price_entry.get()) + note = note_entry.get() + date_str = date_entry.get().strip() + + # 验证日期格式 + try: + datetime.strptime(date_str, "%Y-%m-%d") + except ValueError: + messagebox.showerror("错误", "日期格式不正确,请使用YYYY-MM-DD格式") + return + + transaction = { + "product": self.current_product, + "type": "入库", + "weight": f"{weight:.8f}", + "price": f"{price:.8f}", + "note": note, + "time": date_str + } + + # 处理交易记录,支持进度回调 + def progress_callback(current, total, message): + # 简单的进度显示,可以后续扩展为进度条 + print(f"进度: {current}/{total} - {message}") + + result = self.manager.process_transaction(transaction, progress_callback) + + if result["success"]: + if result["recalculated_count"] > 0: + messagebox.showinfo("成功", f"入库记录添加成功!\n重计算了{result['recalculated_count']}条记录") + else: + messagebox.showinfo("成功", "入库记录添加成功!") + else: + messagebox.showerror("错误", f"入库失败:{result['message']}") + return + + dialog.destroy() + self.create_product_detail_page(self.current_product) + + except ValueError: + messagebox.showerror("错误", "请输入有效的数字") + except Exception as e: + messagebox.showerror("错误", f"入库失败: {str(e)}") + + # 按钮框架 + button_frame = tk.Frame(dialog) + button_frame.pack(pady=20) + + tk.Button(button_frame, text="确认", command=confirm_inbound, bg="lightgreen", width=8).pack(side=tk.LEFT, padx=5) + tk.Button(button_frame, text="取消", command=dialog.destroy, width=8).pack(side=tk.LEFT, padx=5) + + def show_outbound_dialog(self): + """显示出库对话框""" + dialog = tk.Toplevel(self.root) + dialog.title("出库") + dialog.geometry("350x300") + dialog.resizable(False, False) + + # 居中显示 + dialog.transient(self.root) + dialog.grab_set() + + # 计算居中位置 + x = (dialog.winfo_screenwidth() // 2) - (350 // 2) + y = (dialog.winfo_screenheight() // 2) - (300 // 2) + dialog.geometry(f"350x300+{x}+{y}") + + tk.Label(dialog, text=f"产品: {self.current_product}", font=("Arial", 12, "bold")).pack(pady=15) + + tk.Label(dialog, text="重量(kg):").pack() + weight_entry = tk.Entry(dialog, width=20) + weight_entry.pack(pady=5) + + tk.Label(dialog, text="日期(YYYY-MM-DD):").pack() + date_entry = tk.Entry(dialog, width=20) + date_entry.insert(0, datetime.now().strftime("%Y-%m-%d")) # 默认今天日期 + date_entry.pack(pady=5) + + tk.Label(dialog, text="备注:").pack() + note_entry = tk.Entry(dialog, width=20) + note_entry.pack(pady=5) + + # 设置回车键切换输入框 + def focus_next_entry(event, next_entry): + next_entry.focus_set() + return "break" + + def confirm_on_enter(event): + confirm_outbound() + return "break" + + weight_entry.bind("", lambda e: focus_next_entry(e, date_entry)) + date_entry.bind("", lambda e: focus_next_entry(e, note_entry)) + note_entry.bind("", confirm_on_enter) + + # 默认焦点在重量输入框 + weight_entry.focus_set() + + def confirm_outbound(): + try: + weight = float(weight_entry.get()) + note = note_entry.get() + date_str = date_entry.get().strip() + + # 验证日期格式 + try: + datetime.strptime(date_str, "%Y-%m-%d") + except ValueError: + messagebox.showerror("错误", "日期格式不正确,请使用YYYY-MM-DD格式") + return + + # 出库记录,价格将在process_transaction中自动计算 + transaction = { + "product": self.current_product, + "type": "出库", + "weight": f"{weight:.8f}", + "note": note, + "time": date_str + } + + # 处理交易记录,支持进度回调 + def progress_callback(current, total, message): + # 简单的进度显示,可以后续扩展为进度条 + print(f"进度: {current}/{total} - {message}") + + result = self.manager.process_transaction(transaction, progress_callback) + + if result["success"]: + if result["recalculated_count"] > 0: + messagebox.showinfo("成功", f"出库记录添加成功!\n重计算了{result['recalculated_count']}条记录") + else: + messagebox.showinfo("成功", "出库记录添加成功!") + else: + if result['message'] == "库存不足": + messagebox.showerror("错误", "库存不足,无法出库!") + else: + messagebox.showerror("错误", f"出库失败:{result['message']}") + return + + dialog.destroy() + self.create_product_detail_page(self.current_product) + + except ValueError: + messagebox.showerror("错误", "请输入有效的数字") + except Exception as e: + messagebox.showerror("错误", f"出库失败: {str(e)}") + + # 按钮框架 + button_frame = tk.Frame(dialog) + button_frame.pack(pady=20) + + tk.Button(button_frame, text="确认", command=confirm_outbound, bg="lightcoral", width=8).pack(side=tk.LEFT, padx=5) + tk.Button(button_frame, text="取消", command=dialog.destroy, width=8).pack(side=tk.LEFT, padx=5) + + def add_product(self): + """添加新产品""" + name = self.product_name_entry.get().strip() + desc = self.product_desc_entry.get().strip() + + if not name: + messagebox.showerror("错误", "产品名称不能为空") + return + + try: + with open("inventory_data.json", "r+", encoding="utf-8") as f: + data = json.load(f) + + if name in data["products"]: + messagebox.showerror("错误", "产品已存在") + return + + data["products"][name] = { + "total_weight": "0.00000000", + "avg_price": "0.00000000" + } + + f.seek(0) + json.dump(data, f, indent=2, ensure_ascii=False) + f.truncate() + + messagebox.showinfo("成功", "产品添加成功") + self.create_main_page() + + except Exception as e: + messagebox.showerror("错误", f"添加产品失败: {str(e)}") + + def search_products(self): + """搜索产品""" + keyword = self.search_entry.get().strip() + if not keyword: + self.load_products() # 如果没有关键词,显示所有产品 + return + + try: + with open("inventory_data.json", "r", encoding="utf-8") as f: + data = json.load(f) + + # 清空现有数据 + for item in self.product_tree.get_children(): + self.product_tree.delete(item) + + total_value = 0 + total_weight = 0 + found_count = 0 + + # 过滤产品 + for product_name, product_data in data["products"].items(): + if keyword.lower() in product_name.lower(): + weight = float(product_data["total_weight"]) + price = float(product_data["avg_price"]) + value = weight * price + + total_weight += weight + total_value += value + found_count += 1 + + self.product_tree.insert("", tk.END, values=( + product_name, + f"{weight:.2f}", + f"{price:.2f}", + "详情" + )) + + # 更新统计信息 + self.stats_label.config(text=f"搜索结果: {found_count}个产品 库存总价值: {total_value:.2f} 元 库存总重: {total_weight:.2f} kg") + + if found_count == 0: + messagebox.showinfo("搜索结果", f"没有找到包含'{keyword}'的产品") + + except Exception as e: + messagebox.showerror("错误", f"搜索失败: {str(e)}") + + def clear_search(self): + """清空搜索""" + self.search_entry.delete(0, tk.END) + self.load_products() + + def delete_product(self): + """删除产品""" + if messagebox.askyesno("确认", f"确定要删除产品 {self.current_product} 吗?\n\n注意:删除产品将同时删除该产品的所有交易记录!\n如果有大量交易记录,重计算可能需要一些时间。"): + try: + # 进度回调函数 + def progress_callback(current, total, message): + print(f"删除产品进度: {current}/{total} - {message}") + + # 使用manager的删除产品方法 + result = self.manager.delete_product(self.current_product, progress_callback) + + if result["success"]: + if result["deleted_transactions"] > 0: + messagebox.showinfo("成功", f"产品删除成功!\n删除了{result['deleted_transactions']}条相关交易记录") + else: + messagebox.showinfo("成功", "产品删除成功!") + + self.create_main_page() + else: + messagebox.showerror("错误", f"删除产品失败:{result['message']}") + + except Exception as e: + messagebox.showerror("错误", f"删除产品失败: {str(e)}") + + def delete_transaction(self, event): + """删除交易记录""" + selection = self.transaction_tree.selection() + if selection: + # 获取选中的交易记录信息 + item = self.transaction_tree.item(selection[0]) + values = item["values"] + + if len(values) < 6: + messagebox.showerror("错误", "无法获取交易记录信息") + return + + transaction_time = values[0] + transaction_type = values[1] + weight = float(values[2]) + + if messagebox.askyesno("确认", f"确定要删除这条记录吗?\n\n时间: {transaction_time}\n类型: {transaction_type}\n重量: {weight}kg\n\n注意:删除记录将触发重计算,可能需要一些时间。"): + try: + # 查找交易记录的索引 + transaction_index = self.manager.find_transaction_index( + self.current_product, transaction_time, transaction_type, weight + ) + + if transaction_index == -1: + messagebox.showerror("错误", "未找到对应的交易记录") + return + + # 进度回调函数 + def progress_callback(current, total, message): + print(f"删除进度: {current}/{total} - {message}") + + # 删除交易记录 + result = self.manager.delete_transaction(transaction_index, progress_callback) + + if result["success"]: + if result["recalculated_count"] > 0: + messagebox.showinfo("成功", f"交易记录删除成功!\n重计算了{result['recalculated_count']}条记录") + else: + messagebox.showinfo("成功", "交易记录删除成功!") + + # 刷新页面 + self.create_product_detail_page(self.current_product) + else: + messagebox.showerror("错误", f"删除失败:{result['message']}") + + except Exception as e: + messagebox.showerror("错误", f"删除交易记录失败: {str(e)}") def execute_query(self): """执行查询""" - # 获取查询条件 - product_name = self.query_product_var.get().strip().lower() - transaction_type = self.query_type_var.get() - start_date = self.query_start_date_var.get().strip() - end_date = self.query_end_date_var.get().strip() - min_weight = self.query_min_weight_var.get().strip() - max_weight = self.query_max_weight_var.get().strip() - note_keyword = self.query_note_var.get().strip().lower() - - # 过滤交易记录 - filtered_transactions = [] - - for transaction in self.data["transactions"]: - # 产品名称过滤 - if product_name and product_name not in transaction["product"].lower(): - continue - - # 交易类型过滤 - if transaction_type != "全部" and transaction["type"] != transaction_type: - continue - - # 时间范围过滤 - if start_date: - try: - trans_date = transaction["time"][:10] # 取日期部分 - if trans_date < start_date: - continue - except: - pass - - if end_date: - try: - trans_date = transaction["time"][:10] # 取日期部分 - if trans_date > end_date: - continue - except: - pass - - # 重量范围过滤 - if min_weight: - try: - if transaction["weight"] < float(min_weight): - continue - except ValueError: - pass - - if max_weight: - try: - if transaction["weight"] > float(max_weight): - continue - except ValueError: - pass - - # 备注关键词过滤 - if note_keyword and note_keyword not in transaction["note"].lower(): - continue - - filtered_transactions.append(transaction) - - # 按时间倒序排列 - filtered_transactions.sort(key=lambda x: x["time"], reverse=True) - - # 保存查询结果 - self.query_results = filtered_transactions - - # 重置到第一页 - self.current_query_page = 1 - - # 更新查询结果显示 - self.update_query_results() - - def update_query_results(self): - """更新查询结果显示""" - for widget in self.query_result_frame.winfo_children(): - widget.destroy() - - # 分页处理 - start_idx = (self.current_query_page - 1) * self.items_per_page - end_idx = start_idx + self.items_per_page - page_results = self.query_results[start_idx:end_idx] - - # 显示查询结果 - for transaction in page_results: - row_frame = tk.Frame(self.query_result_frame) - row_frame.pack(fill=tk.X, pady=1) - - # 计算总价 - total_price = transaction["weight"] * transaction["price"] - - tk.Label(row_frame, text=transaction["time"], width=15, relief=tk.RIDGE).pack(side=tk.LEFT) - tk.Label(row_frame, text=transaction["product"], width=15, relief=tk.RIDGE).pack(side=tk.LEFT) - - color = "lightgreen" if transaction["type"] == "入库" else "lightcoral" - tk.Label(row_frame, text=transaction["type"], width=14, relief=tk.RIDGE, bg=color).pack(side=tk.LEFT) - - tk.Label(row_frame, text=f"{transaction['weight']:.1f}", width=15, relief=tk.RIDGE).pack(side=tk.LEFT) - tk.Label(row_frame, text=f"{transaction['price']:.2f}", width=15, relief=tk.RIDGE).pack(side=tk.LEFT) - tk.Label(row_frame, text=f"{total_price:.2f}", width=15, relief=tk.RIDGE).pack(side=tk.LEFT) - tk.Label(row_frame, text=transaction["note"], width=15, relief=tk.RIDGE).pack(side=tk.LEFT) - - # 更新分页信息 - total_results = len(self.query_results) - max_pages = max(1, (total_results + self.items_per_page - 1) // self.items_per_page) - self.query_page_label.config(text=f"第 {self.current_query_page} 页 / 共 {max_pages} 页 (共 {total_results} 条记录)") - - # 计算统计信息 - total_weight = 0 - total_price = 0 - for transaction in self.query_results: - total_weight += transaction["weight"] - total_price += transaction["weight"] * transaction["price"] - - # 在分页信息右侧显示统计信息 - stats_text = f"总重量: {total_weight:.2f}kg 总价: {total_price:.2f}元" - if hasattr(self, 'query_stats_label') and self.query_stats_label.winfo_exists(): - try: - self.query_stats_label.config(text=stats_text) - except tk.TclError: - # 如果标签已被销毁,重新创建 - self.query_stats_label = tk.Label(self.query_page_frame, text=stats_text, font=("Arial", 10, "bold"), fg="blue") - self.query_stats_label.pack(side=tk.RIGHT) - else: - self.query_stats_label = tk.Label(self.query_page_frame, text=stats_text, font=("Arial", 10, "bold"), fg="blue") - self.query_stats_label.pack(side=tk.RIGHT) - - # 如果没有查询结果,显示提示 - if not page_results: - no_result_frame = tk.Frame(self.query_result_frame) - no_result_frame.pack(fill=tk.X, pady=20) - tk.Label(no_result_frame, text="未找到符合条件的记录", - font=("Arial", 12), fg="gray").pack() - - def clear_query_conditions(self): - """清空查询条件""" - self.query_product_var.set("") - self.query_type_var.set("全部") - self.query_start_date_var.set("") - self.query_end_date_var.set("") - self.query_min_weight_var.set("") - self.query_max_weight_var.set("") - self.query_note_var.set("") - self.execute_query() - - def prev_query_page(self): - """查询结果上一页""" - if self.current_query_page > 1: - self.current_query_page -= 1 - self.update_query_results() - - def next_query_page(self): - """查询结果下一页""" - total_results = len(self.query_results) - max_pages = max(1, (total_results + self.items_per_page - 1) // self.items_per_page) - - if self.current_query_page < max_pages: - self.current_query_page += 1 - self.update_query_results() - - def delete_transaction(self, transaction): - """删除交易记录""" - # 确认删除 - result = messagebox.askyesno("确认删除", - f"确定要删除这条交易记录吗?\n\n时间: {transaction['time']}\n类型: {transaction['type']}\n重量: {transaction['weight']:.1f}kg\n价格: {transaction['price']:.2f}元/kg") - - if not result: - return - try: - # 从交易记录中移除 - self.data["transactions"].remove(transaction) + with open("inventory_data.json", "r", encoding="utf-8") as f: + data = json.load(f) - # 重新计算产品数据 - self.recalculate_product_data(self.current_product) + # 获取查询条件 + product_name = self.query_product_entry.get().strip() + transaction_type = self.query_type_var.get() + start_date = self.start_date_entry.get().strip() + end_date = self.end_date_entry.get().strip() + min_weight = self.min_weight_entry.get().strip() + max_weight = self.max_weight_entry.get().strip() + note_keyword = self.note_keyword_entry.get().strip() - # 保存数据 - self.save_data() + # 过滤交易记录 + self.filtered_transactions = [] + for transaction in data["transactions"]: + if transaction.get("is_snapshot"): + continue + + # 应用过滤条件 + if self._match_query_conditions(transaction, product_name, transaction_type, + start_date, end_date, min_weight, max_weight, note_keyword): + self.filtered_transactions.append(transaction) - messagebox.showinfo("成功", "交易记录删除成功!") + # 按时间倒序排序(最新的在前面) + self.filtered_transactions.sort(key=lambda x: x["time"], reverse=True) - # 刷新详情页面 - self.show_detail_page(self.current_product) + self.total_records = len(self.filtered_transactions) + self.current_page = 1 # 重置到第一页 - except ValueError: - messagebox.showerror("错误", "删除失败,请重试") + # 显示当前页的数据 + self._display_current_page() + + if self.total_records == 0: + messagebox.showinfo("查询结果", "没有找到符合条件的记录") + + except Exception as e: + messagebox.showerror("错误", f"查询失败: {str(e)}") - def recalculate_product_data(self, product_name): - """重新计算产品数据""" - # 获取该产品的所有交易记录 - product_transactions = [t for t in self.data["transactions"] if t["product"] == product_name] + def _display_current_page(self): + """显示当前页的数据""" + # 清空现有数据 + for item in self.query_tree.get_children(): + self.query_tree.delete(item) - if not product_transactions: - # 如果没有交易记录,重置产品数据 - self.data["products"][product_name] = {"total_weight": 0, "avg_price": 0} - return + # 计算分页 + start_index = (self.current_page - 1) * self.page_size + end_index = min(start_index + self.page_size, self.total_records) - # 重新计算总重量和平均价格 total_weight = 0 total_value = 0 - for transaction in product_transactions: + # 显示当前页的记录 + for i in range(start_index, end_index): + transaction = self.filtered_transactions[i] + weight = float(transaction["weight"]) + price = float(transaction["price"]) + total_price = weight * price + + # 计算统计信息(入库为正,出库为负) if transaction["type"] == "入库": - total_weight += transaction["weight"] - total_value += transaction["weight"] * transaction["price"] - else: # 出库 - total_weight -= transaction["weight"] + total_weight += weight + else: + total_weight -= weight + total_value += total_price + + # 设置行颜色 + tag = "inbound" if transaction["type"] == "入库" else "outbound" + + self.query_tree.insert("", tk.END, values=( + transaction["time"], + transaction["product"], + transaction["type"], + f"{weight:.2f}", + f"{price:.2f}", + f"{total_price:.2f}", + transaction.get("note", "") + ), tags=(tag,)) - # 计算平均价格(只考虑入库记录) - inbound_transactions = [t for t in product_transactions if t["type"] == "入库"] - if inbound_transactions: - total_inbound_weight = sum(t["weight"] for t in inbound_transactions) - total_inbound_value = sum(t["weight"] * t["price"] for t in inbound_transactions) - avg_price = total_inbound_value / total_inbound_weight if total_inbound_weight > 0 else 0 - else: - avg_price = 0 + # 设置标签颜色 + self.query_tree.tag_configure("inbound", background="lightgreen") + self.query_tree.tag_configure("outbound", background="lightcoral") - # 更新产品数据 - self.data["products"][product_name] = { - "total_weight": max(0, total_weight), # 确保重量不为负 - "avg_price": avg_price - } + # 更新分页信息 + total_pages = (self.total_records + self.page_size - 1) // self.page_size if self.total_records > 0 else 1 + self.page_info_label.config(text=f"第 {self.current_page} 页 / 共 {total_pages} 页 (共 {self.total_records} 条记录)") + + # 更新按钮状态 + self.prev_button.config(state=tk.NORMAL if self.current_page > 1 else tk.DISABLED) + self.next_button.config(state=tk.NORMAL if self.current_page < total_pages else tk.DISABLED) + + # 计算全部记录的统计信息 + all_total_weight = 0 + all_total_value = 0 + for transaction in self.filtered_transactions: + weight = float(transaction["weight"]) + price = float(transaction["price"]) + total_price = weight * price + + if transaction["type"] == "入库": + all_total_weight += weight + else: + all_total_weight -= weight + all_total_value += total_price + + # 更新统计信息 + self.query_stats_label.config(text=f"查询结果: {self.total_records}条记录 净重量: {all_total_weight:.2f}kg 总价值: {all_total_value:.2f}元") - def delete_product_category(self): - """删除整个产品分类""" - # 确认删除 - result = messagebox.askyesno("确认删除", - f"确定要删除产品分类 '{self.current_product}' 吗?\n\n这将删除该产品的所有交易记录,此操作不可撤销!") + def prev_page(self): + """上一页""" + if self.current_page > 1: + self.current_page -= 1 + self._display_current_page() + + def next_page(self): + """下一页""" + total_pages = (self.total_records + self.page_size - 1) // self.page_size if self.total_records > 0 else 1 + if self.current_page < total_pages: + self.current_page += 1 + self._display_current_page() + + def on_page_size_change(self, event): + """每页显示数量改变""" + try: + self.page_size = int(self.page_size_var.get()) + self.current_page = 1 # 重置到第一页 + if hasattr(self, 'filtered_transactions') and self.filtered_transactions: + self._display_current_page() + except ValueError: + pass + + def quick_query(self, period): + """快速查询""" + from datetime import datetime, timedelta - if not result: - return + # 清空查询条件 + self.clear_query_conditions() + + today = datetime.now() + + if period == "today": + # 今日 + date_str = today.strftime("%Y-%m-%d") + self.start_date_entry.delete(0, tk.END) + self.start_date_entry.insert(0, date_str) + self.end_date_entry.delete(0, tk.END) + self.end_date_entry.insert(0, date_str) + elif period == "week": + # 本周(周一到今天) + days_since_monday = today.weekday() + monday = today - timedelta(days=days_since_monday) + self.start_date_entry.delete(0, tk.END) + self.start_date_entry.insert(0, monday.strftime("%Y-%m-%d")) + self.end_date_entry.delete(0, tk.END) + self.end_date_entry.insert(0, today.strftime("%Y-%m-%d")) + elif period == "month": + # 本月(月初到今天) + month_start = today.replace(day=1) + self.start_date_entry.delete(0, tk.END) + self.start_date_entry.insert(0, month_start.strftime("%Y-%m-%d")) + self.end_date_entry.delete(0, tk.END) + self.end_date_entry.insert(0, today.strftime("%Y-%m-%d")) + elif period == "all": + # 全部记录 + self.start_date_entry.delete(0, tk.END) + self.start_date_entry.insert(0, "2020-01-01") + self.end_date_entry.delete(0, tk.END) + self.end_date_entry.insert(0, "2030-12-31") + + # 执行查询 + self.execute_query() + + def clear_query_conditions(self): + """清空查询条件""" + self.query_product_entry.delete(0, tk.END) + self.query_type_var.set("全部") + self.start_date_entry.delete(0, tk.END) + self.start_date_entry.insert(0, "2025-06-26") + self.end_date_entry.delete(0, tk.END) + self.end_date_entry.insert(0, "2025-07-26") + self.min_weight_entry.delete(0, tk.END) + self.max_weight_entry.delete(0, tk.END) + self.note_keyword_entry.delete(0, tk.END) + + def _match_query_conditions(self, transaction, product_name, transaction_type, + start_date, end_date, min_weight, max_weight, note_keyword): + """检查交易记录是否匹配查询条件""" + from datetime import datetime + + # 产品名称过滤 + if product_name and product_name.lower() not in transaction["product"].lower(): + return False + + # 交易类型过滤 + if transaction_type != "全部" and transaction["type"] != transaction_type: + return False + + # 时间范围过滤 + try: + if start_date: + start_dt = datetime.strptime(start_date, "%Y-%m-%d") + txn_dt = datetime.strptime(transaction["time"].split()[0], "%Y-%m-%d") + if txn_dt < start_dt: + return False + except ValueError: + pass # 忽略日期格式错误 try: - # 删除该产品的所有交易记录 - self.data["transactions"] = [t for t in self.data["transactions"] if t["product"] != self.current_product] + if end_date: + end_dt = datetime.strptime(end_date, "%Y-%m-%d") + txn_dt = datetime.strptime(transaction["time"].split()[0], "%Y-%m-%d") + if txn_dt > end_dt: + return False + except ValueError: + pass # 忽略日期格式错误 + + # 重量范围过滤 + weight = float(transaction["weight"]) + try: + if min_weight and weight < float(min_weight): + return False + except ValueError: + pass + + try: + if max_weight and weight > float(max_weight): + return False + except ValueError: + pass + + # 备注关键词过滤 + if note_keyword: + note = transaction.get("note", "") + if note_keyword.lower() not in note.lower(): + return False + + return True + + def print_query_results(self): + """打印查询结果""" + try: + # 获取当前查询结果 + results = [] + for item in self.query_tree.get_children(): + values = self.query_tree.item(item)["values"] + results.append(values) - # 删除产品数据 - if self.current_product in self.data["products"]: - del self.data["products"][self.current_product] + if not results: + messagebox.showwarning("提示", "没有查询结果可以打印") + return - # 保存数据 - self.save_data() + # 创建打印预览窗口 + print_window = tk.Toplevel(self.root) + print_window.title("打印预览") + print_window.geometry("800x600") + print_window.resizable(True, True) - messagebox.showinfo("成功", f"产品分类 '{self.current_product}' 已删除!") + # 创建文本框显示打印内容 + text_frame = tk.Frame(print_window) + text_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) - # 返回主页 - self.show_main_page() + text_widget = tk.Text(text_frame, wrap=tk.NONE, font=("Courier", 10)) + scrollbar_y = tk.Scrollbar(text_frame, orient=tk.VERTICAL, command=text_widget.yview) + scrollbar_x = tk.Scrollbar(text_frame, orient=tk.HORIZONTAL, command=text_widget.xview) + text_widget.config(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set) + + scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y) + scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X) + text_widget.pack(fill=tk.BOTH, expand=True) + + # 生成打印内容 + from datetime import datetime + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + print_content = f"""库存管理系统 - 查询结果报表 +生成时间: {current_time} +{'='*80} + +""" + + # 表头 + header = f"{'时间':<20} {'产品名':<15} {'类型':<8} {'重量(kg)':<10} {'价格(元/kg)':<12} {'总价(元)':<12} {'备注':<20}" + print_content += header + "\n" + print_content += "-" * 80 + "\n" + + # 数据行 + for values in results: + line = f"{values[0]:<20} {values[1]:<15} {values[2]:<8} {values[3]:<10} {values[4]:<12} {values[5]:<12} {values[6]:<20}" + print_content += line + "\n" + + # 统计信息 + stats_text = self.query_stats_label.cget("text") + print_content += "\n" + "="*80 + "\n" + print_content += f"统计信息: {stats_text}\n" + print_content += f"记录总数: {len(results)}条\n" + + text_widget.insert(tk.END, print_content) + text_widget.config(state=tk.DISABLED) + + # 按钮框架 + button_frame = tk.Frame(print_window) + button_frame.pack(pady=10) + + def copy_to_clipboard(): + print_window.clipboard_clear() + print_window.clipboard_append(print_content) + messagebox.showinfo("成功", "内容已复制到剪贴板") + + tk.Button(button_frame, text="复制到剪贴板", command=copy_to_clipboard, bg="lightblue").pack(side=tk.LEFT, padx=5) + tk.Button(button_frame, text="关闭", command=print_window.destroy).pack(side=tk.LEFT, padx=5) except Exception as e: - messagebox.showerror("错误", f"删除失败:{str(e)}") - -def main(): - root = tk.Tk() - app = InventoryApp(root) - root.mainloop() + messagebox.showerror("错误", f"生成打印预览失败: {str(e)}") + + def run(self): + """运行应用""" + self.root.mainloop() if __name__ == "__main__": - main() \ No newline at end of file + app = InventoryUI() + app.run() \ No newline at end of file