def open_in_transit_window():
if not cursor:
messagebox.showwarning("警告", "数据库未连接,无法管理在途数据")
return
global in_transit_open
if in_transit_open:
messagebox.showwarning("提示", "在途未入库窗口已打开。")
return
in_transit_open = True
window = tk.Toplevel(root)
window.title("在途未入库管理")
window.geometry("1180x650")
window.resizable(True, True)
item_var = tk.StringVar()
qty_var = tk.StringVar()
so_var_local = tk.StringVar()
sn_var_local = tk.StringVar()
remark_var = tk.StringVar()
search_var = tk.StringVar()
tree = ttk.Treeview(
window,
columns=("物料编号", "数量", "销售订单", "序列号", "备注", "发货日期", "已入库"),
show="headings",
height=22
)
headers = ["物料编号", "数量", "销售订单", "序列号", "备注", "发货日期", "已入库"]
widths = [150, 80, 120, 180, 150, 120, 80]
for header, width in zip(headers, widths):
tree.heading(header, text=header)
tree.column(header, width=width, anchor='center')
tree.grid(row=8, column=0, columnspan=4, padx=10, pady=10, sticky="nsew")
v_scroll = ttk.Scrollbar(window, orient="vertical", command=tree.yview)
h_scroll = ttk.Scrollbar(window, orient="horizontal", command=tree.xview)
tree.configure(yscrollcommand=v_scroll.set, xscrollcommand=h_scroll.set)
v_scroll.grid(row=8, column=4, sticky='ns')
h_scroll.grid(row=9, column=0, columnspan=4, sticky='ew')
def refresh_list(filter_text=""):
for row in tree.get_children(): tree.delete(row)
try:
if filter_text.strip():
query = """SELECT Item, Qty, SO, SN, 备注, 发货日期, ImportedQty FROM in_transit WHERE Item LIKE %s"""
cursor.execute(query, (f"%{filter_text}%",))
else:
cursor.execute("SELECT Item, Qty, SO, SN, 备注, 发货日期, ImportedQty FROM in_transit")
for row in cursor.fetchall():
values = [str(row[i] or "") for i in range(6)]
values.append(str(row[6] or "0"))
tree.insert("", tk.END, values=values)
except Exception as e:
messagebox.showerror("读取失败", f"无法加载在途数据:\n{e}")
def add_record():
item = item_var.get().strip()
qty_str = qty_var.get().strip()
so = so_var_local.get().strip()
sn = sn_var_local.get().strip()
remark = remark_var.get().strip()
if not item or not qty_str: return
try:
qty = float(qty_str)
if qty <= 0: raise ValueError
except ValueError:
messagebox.showwarning("提示", "数量必须是大于0的有效数字")
return
ship_date = datetime.now().strftime("%Y-%m-%d %H:%M")
try:
cursor.execute("INSERT INTO in_transit (Item, Qty, SO, SN, 备注, 发货日期, ImportedQty) VALUES (%s,%s,%s,%s,%s,%s,%s)",
(item, qty, so, sn, remark, ship_date, 0))
conn.commit()
messagebox.showinfo("成功", "✅ 记录已添加")
clear_inputs()
refresh_list()
except Exception as e:
conn.rollback()
messagebox.showerror("错误", f"添加失败:\n{e}")
def delete_record():
selected = tree.selection()
if not selected: return
item = tree.item(selected[0])['values'][0]
sn = tree.item(selected[0])['values'][3]
if messagebox.askyesno("确认删除", f"确定要删除该在途记录?\n物料编号:{item}\n序列号:{sn}"):
try:
cursor.execute("DELETE FROM in_transit WHERE Item = %s AND SN = %s", (item, sn))
conn.commit()
messagebox.showinfo("成功", "🗑️ 记录已删除")
refresh_list()
except Exception as e:
conn.rollback()
messagebox.showerror("错误", f"删除失败:\n{e}")
def on_double_click(event):
selected = tree.selection()
if not selected: return
values = tree.item(selected[0])['values']
original_item, original_sn = values[0], values[3]
edit_win = tk.Toplevel(window)
edit_win.title("编辑在途记录")
edit_win.geometry("500x420")
edit_win.resizable(False, False)
vars = [tk.StringVar(value=v) for v in values[:-1]]
fields = ["物料编号", "数量", "销售订单", "序列号", "备注", "发货日期(只读)"]
for i, (label, var) in enumerate(zip(fields, vars)):
ttk.Label(edit_win, text=label).grid(row=i, column=0, padx=15, pady=6, sticky='e')
if "只读" not in label:
ttk.Entry(edit_win, textvariable=var, width=30).grid(row=i, column=1, padx=15, pady=6, sticky='w')
else:
readonly_entry = ttk.Entry(edit_win, textvariable=var, state='readonly', width=30)
readonly_entry.grid(row=i, column=1, padx=15, pady=6, sticky='w')
def save_edit():
new_item = vars[0].get().strip()
new_qty = vars[1].get().strip()
new_so = vars[2].get().strip()
new_sn = vars[3].get().strip()
new_remark = vars[4].get().strip()
if not new_item or not new_qty: return
try: float(new_qty)
except ValueError: return
try:
cursor.execute("UPDATE in_transit SET Item=%s,Qty=%s,SO=%s,SN=%s,备注=%s WHERE Item=%s AND SN=%s",
(new_item, new_qty, new_so, new_sn, new_remark, original_item, original_sn))
conn.commit()
messagebox.showinfo("成功", "💾 修改已保存")
edit_win.destroy()
refresh_list()
window.lift()
except Exception as e:
conn.rollback()
messagebox.showerror("错误", f"更新失败:\n{e}")
ttk.Button(edit_win, text="保存修改", command=save_edit).grid(row=len(fields), column=0, columnspan=2, pady=20)
def clear_inputs():
item_var.set(""); qty_var.set(""); so_var_local.set(""); sn_var_local.set(""); remark_var.set("")
def copy_selected():
selected = tree.selection()
if selected:
values = tree.item(selected[0])['values']
text = '\t'.join(str(v) for v in values)
window.clipboard_clear()
window.clipboard_append(text)
def show_context_menu(event):
if tree.identify_row(event.y):
tree.selection_set(tree.identify_row(event.y))
context_menu.post(event.x_root, event.y_root)
def import_from_excel():
filepath = filedialog.askopenfilename(filetypes=[("Excel 文件", "*.xlsx"), ("所有文件", "*.*")])
if not filepath: return
if not messagebox.askyesno("⚠️ 确认操作", "清空当前所有在途数据,并用Excel中的数据完全替换?"): return
try:
df = pd.read_excel(filepath)
required_columns = {'Item', 'Qty', 'SO', 'SN', '备注', '发货日期'}
if missing := required_columns - set(df.columns):
messagebox.showerror("格式错误", f"缺少必要列:{missing}")
return
# 数据清洗
df['Item'] = df['Item'].astype(str).str.strip()
df['Qty'] = pd.to_numeric(df['Qty'], errors='coerce').fillna(0)
df['SO'] = df['SO'].astype(str).str.strip()
df['SN'] = df['SN'].astype(str).str.strip()
df['备注'] = df['备注'].astype(str).str.strip()
df['发货日期'] = df['发货日期'].astype(str).str.strip()
df.dropna(subset=['Item'], inplace=True)
cursor.execute("DELETE FROM in_transit")
inserted_count = 0
for _, row in df.iterrows():
cursor.execute("INSERT INTO in_transit (Item, Qty, SO, SN, 备注, 发货日期, ImportedQty) VALUES (%s,%s,%s,%s,%s,%s,%s)",
(row['Item'], row['Qty'], row['SO'], row['SN'], row['备注'], row['发货日期'], 0))
inserted_count += 1
conn.commit()
messagebox.showinfo("导入成功", f"✅ 已成功导入 {inserted_count} 条记录!")
refresh_list()
except Exception as e:
conn.rollback()
messagebox.showerror("导入失败", f"发生错误:\n{str(e)}")
def import_from_excel_append():
"""从 Excel 追加导入在途数据(允许重复,不跳过任何行)"""
filepath = filedialog.askopenfilename(
title="选择在途数据Excel文件(追加导入,允许重复)",
filetypes=[("Excel 文件", "*.xlsx"), ("所有文件", "*.*")]
)
if not filepath:
return
try:
df = pd.read_excel(filepath)
required_columns = {'Item', 'Qty', 'SO', 'SN', '备注', '发货日期'}
missing = required_columns - set(df.columns)
if missing:
messagebox.showerror("格式错误", f"❌ 缺少必要列:{missing}\n请确保包含以下列:\n{required_columns}")
return
# 数据清洗
df['Item'] = df['Item'].astype(str).str.strip()
df['Qty'] = pd.to_numeric(df['Qty'], errors='coerce').fillna(0)
df['SO'] = df['SO'].astype(str).str.strip()
df['SN'] = df['SN'].astype(str).str.strip()
df['备注'] = df['备注'].astype(str).str.strip()
df['发货日期'] = df['发货日期'].astype(str).str.strip()
# 过滤有效行:仅保留非空物料编号
valid_df = df.dropna(subset=['Item']).query('Item != "nan" and Item != ""')
if valid_df.empty:
messagebox.showwarning("无有效数据", "Excel 中没有可导入的有效数据行。")
return
# 开始插入(无条件追加)
inserted_count = 0
for _, row in valid_df.iterrows():
try:
cursor.execute("""
INSERT INTO in_transit (Item, Qty, SO, SN, 备注, 发货日期, ImportedQty)
VALUES (%s, %s, %s, %s, %s, %s, %s)
""", (row['Item'], row['Qty'], row['SO'], row['SN'], row['备注'], row['发货日期'], 0))
inserted_count += 1
except Exception as e:
print(f"插入失败(忽略): {e}") # 日志输出,不影响整体导入
conn.commit()
messagebox.showinfo("追加导入完成", f"✅ 已无条件追加 {inserted_count} 条记录(含可能的重复)")
refresh_list()
except Exception as e:
conn.rollback()
messagebox.showerror("导入失败", f"发生错误:\n{str(e)}")
inputs = [("物料编号", item_var), ("数量", qty_var), ("销售订单", so_var_local), ("序列号", sn_var_local), ("备注", remark_var)]
for i, (label, var) in enumerate(inputs):
ttk.Label(window, text=label).grid(row=i, column=0, padx=10, pady=4, sticky='e')
ttk.Entry(window, textvariable=var, width=25).grid(row=i, column=1, padx=10, pady=4, sticky='w')
ttk.Label(window, text="发货日期:").grid(row=5, column=0, padx=10, pady=4, sticky='e')
date_label = tk.Label(window, text=datetime.now().strftime("%Y-%m-%d %H:%M"), fg="blue", font=("Arial", 12, "bold"))
date_label.grid(row=5, column=1, padx=10, pady=4, sticky='w')
ttk.Label(window, text="* 新增记录将使用当前日期作为发货日期", foreground="gray", font=("Arial", 10)) \
.grid(row=6, column=0, columnspan=2, padx=10, pady=2, sticky='w')
# 创建按钮框架,并左对齐
button_frame = ttk.Frame(window)
button_frame.grid(row=7, column=0, columnspan=4, pady=10, sticky='w', padx=(10, 0))
# 在 frame 内部使用 pack 左对齐,并 anchor='w' 确保紧贴左边
ttk.Button(button_frame, text="➕ 添加记录", command=add_record).pack(side="left", padx=5)
ttk.Button(button_frame, text="🗑️ 删除选中", command=delete_record).pack(side="left", padx=5)
ttk.Button(button_frame, text="🔄 从Excel导入并覆盖", command=import_from_excel).pack(side="left", padx=5)
ttk.Button(button_frame, text="📥 追加导入(允许重复)", command=import_from_excel_append).pack(side="left", padx=5)
# 可选:防止 frame 自动扩展居中
button_frame.columnconfigure(0, weight=0) # 禁止拉伸
ttk.Label(window, text="🔍 搜索").grid(row=7, column=2, padx=10, pady=9, sticky='e')
ttk.Entry(window, textvariable=search_var, width=30).grid(row=7, column=3, padx=10, pady=9, sticky='w')
search_var.trace_add("write", lambda *args: refresh_list(search_var.get()))
context_menu = tk.Menu(window, tearoff=0); context_menu.add_command(label="📋 复制整行", command=copy_selected)
tree.bind("<Button-3>", show_context_menu); tree.bind("<Double-1>", on_double_click)
refresh_list()
def on_close():
global in_transit_open; in_transit_open = False; window.destroy()
window.protocol("WM_DELETE_WINDOW", on_close)
window.grid_rowconfigure(8, weight=1); window.grid_columnconfigure(1, weight=1)
window.transient(root); window.grab_set()
修改隐藏按钮和输入框 提供完整修改代码
最新发布