import osimport tkinter as tkfrom tkinter import ttk, filedialog, messagebox, simpledialogimport pandas as pdfrom openpyxl import load_workbookfrom collections import defaultdict# ===================== 配置映射 =====================cost_subject_code_map = { "管理费用": "660214", "销售费用": "660101", "研发支出": "5301010802", "A车间": "51010121/***", "B车间": "51010121/***", "C车间": "51010121/***", "D车间": "51010121/***", "E分厂": "51010121/***"}tax_rate_code_map = { 1: "2221010111", 6: "2221010103", 9: "2221010109"}# ===================== 主界面类 =====================class ExpenseSummaryGUI: def __init__(self, root): self.root = root self.root.title("差旅费信息汇总工具") self.root.state("zoomed") # 默认最大化 self.root.geometry("1200x700") # 样式美化 self.style = ttk.Style() self.style.theme_use("clam") self.style.configure("Title.TLabel", font=("微软雅黑", 16, "bold"), padding=10) self.style.configure("Normal.TLabel", font=("微软雅黑", 11)) self.style.configure("Btn.TButton", font=("微软雅黑", 11), padding=6) self.folder_path = tk.StringVar() self.invoice_file = None self.reimburse_file = None self.travel_file = None self.create_widgets() def create_widgets(self): # 主容器 main_frame = ttk.Frame(self.root, padding=30) main_frame.pack(fill=tk.BOTH, expand=True) # 标题 ttk.Label(main_frame, text="差旅费信息批量汇总工具", style="Title.TLabel").pack() # 文件夹选择区域 path_frame = ttk.Frame(main_frame) path_frame.pack(fill=tk.X, pady=20) ttk.Label(path_frame, text="选择目标文件夹:", style="Normal.TLabel").pack(side=tk.LEFT) ttk.Entry(path_frame, textvariable=self.folder_path, font=("微软雅黑", 10), width=80).pack(side=tk.LEFT, padx=10, fill=tk.X, expand=True) # 修复:pad=5 改为正确的 padx=5 ttk.Button(path_frame, text="浏览", command=self.select_folder, style="Btn.TButton").pack(side=tk.LEFT, padx=5) # 文件识别提示框 info_frame = ttk.LabelFrame(main_frame, text="已识别Excel文件", padding=15) info_frame.pack(fill=tk.X, pady=10) self.label_invoice = ttk.Label(info_frame, text="发票信息提取结果:未识别", style="Normal.TLabel") self.label_invoice.pack(anchor="w", pady=4) self.label_reimburse = ttk.Label(info_frame, text="报销单:未识别", style="Normal.TLabel") self.label_reimburse.pack(anchor="w", pady=4) self.label_travel = ttk.Label(info_frame, text="差旅费信息汇总表:未识别", style="Normal.TLabel") self.label_travel.pack(anchor="w", pady=4) # 空白占位 ttk.Frame(main_frame).pack(expand=True) # 操作按钮 btn_frame = ttk.Frame(main_frame) btn_frame.pack(pady=20) self.btn_start = ttk.Button(btn_frame, text="开始汇总处理", command=self.start_process, style="Btn.TButton", state=tk.DISABLED) self.btn_start.pack() def select_folder(self): folder = filedialog.askdirectory(title="选择存放3个Excel文件的文件夹") if not folder: return self.folder_path.set(folder) self.match_excel_files(folder) def match_excel_files(self, folder): excel_files = [f for f in os.listdir(folder) if f.lower().endswith((".xlsx", ".xls"))] self.invoice_file = None self.reimburse_file = None self.travel_file = None for fname in excel_files: if "发票信息提取结果" in fname: self.invoice_file = os.path.join(folder, fname) elif "报销单" in fname: self.reimburse_file = os.path.join(folder, fname) elif "差旅费信息汇总表" in fname: self.travel_file = os.path.join(folder, fname) # 更新界面显示 self.label_invoice.config(text=f"发票信息提取结果:{os.path.basename(self.invoice_file) if self.invoice_file else'未识别'}") self.label_reimburse.config(text=f"报销单:{os.path.basename(self.reimburse_file) if self.reimburse_file else'未识别'}") self.label_travel.config(text=f"差旅费信息汇总表:{os.path.basename(self.travel_file) if self.travel_file else'未识别'}") if self.invoice_file and self.reimburse_file and self.travel_file: self.btn_start.config(state=tk.NORMAL) else: self.btn_start.config(state=tk.DISABLED) messagebox.showwarning("文件缺失", "未能识别全部3个Excel文件,请检查文件名!\n需要包含:发票信息提取结果、报销单、差旅费信息汇总表") def start_process(self): try: self.process_main() messagebox.showinfo("处理完成", "数据汇总成功!已写入【差旅费信息汇总表】") except Exception as e: messagebox.showerror("处理异常", f"程序出错:{str(e)}") def process_main(self): # 1.读取发票表数据 df_invoice = pd.read_excel(self.invoice_file, sheet_name=0) df_invoice.columns = [str(col).strip() for col in df_invoice.columns] invoice_dict = defaultdict(lambda: {"amount4": 0.0, "tax_detail": defaultdict(float)}) for idx, row in df_invoice.iterrows(): # 序号容错处理 try: seq = int(row.iloc[0]) except: continue # 第四列金额 col4 = 0.0 if pd.notna(row.iloc[3]): try: col4 = float(row.iloc[3]) except: pass # 处理带%的税率文本 tax_raw = row.iloc[5] tax_val = 0.0 if pd.notna(tax_raw): tax_str = str(tax_raw).strip() # 去除百分号 if "%" in tax_str: tax_str = tax_str.replace("%", "") try: tax_val = float(tax_str) except: continue # 税额 tax_amt = 0.0 if pd.notna(row.iloc[4]): try: tax_amt = float(row.iloc[4]) except: pass invoice_dict[seq]["amount4"] += col4 invoice_dict[seq]["tax_detail"][tax_val] += tax_amt # 2.读取报销单所有sheet wb_reimb = load_workbook(self.reimburse_file, data_only=True) sheet_names = [s for s in wb_reimb.sheetnames if s.isdigit()] sheet_names = sorted(sheet_names, key=int) result_rows = [] # 表头 headers = [ "序号", "摘要", "费用科目", "科目代码", "费用金额", "税率一", "税额一", "税率二", "税额二", "税率三", "税额三", "内部银行存款", "内行金额" ] for row_no, sheet_name in enumerate(sheet_names, start=1): seq = int(sheet_name) ws = wb_reimb[sheet_name] # ②摘要 V2 + V3 v2 = str(ws["V2"].value).strip() if ws["V2"].value else "" v3 = str(ws["V3"].value).strip() if ws["V3"].value else "" summary = f"{v2}报差旅费({v3})" # ③费用科目 W1 cost_subject = str(ws["W1"].value).strip() if ws["W1"].value else "" # ④科目代码 subject_code = cost_subject_code_map.get(cost_subject, "") if subject_code == "5301010802": proj_code = simpledialog.askstring("研发项目编码", "因该项差旅费入研发,请输入研发项目编码:") if proj_code: subject_code = f"5301010802/{proj_code.strip()}" # ⑤费用金额:R列+S列合计 + 发票第4列同序号合计 r_sum = 0.0 s_sum = 0.0 max_row = ws.max_row for r in range(1, max_row + 1): rv = ws[f"R{r}"].value sv = ws[f"S{r}"].value if isinstance(rv, (int, float)): r_sum += rv if isinstance(sv, (int, float)): s_sum += sv inv_amt4 = invoice_dict[seq]["amount4"] total_cost = r_sum + s_sum + inv_amt4 # 税率处理 tax_data = invoice_dict[seq]["tax_detail"] # 只保留预设的1/6/9税率,过滤异常税率 valid_taxes = {k: v for k, v in tax_data.items() if k in (1,6,9)} sorted_taxes = sorted(valid_taxes.keys()) # 升序 1,6,9 tax1_code, tax1_amt = "", 0.0 tax2_code, tax2_amt = "", 0.0 tax3_code, tax3_amt = "", 0.0 if len(sorted_taxes) >= 1: t1 = sorted_taxes[0] tax1_code = tax_rate_code_map.get(t1, "") tax1_amt = valid_taxes[t1] if len(sorted_taxes) >= 2: t2 = sorted_taxes[1] tax2_code = tax_rate_code_map.get(t2, "") tax2_amt = valid_taxes[t2] if len(sorted_taxes) >= 3: t3 = sorted_taxes[2] tax3_code = tax_rate_code_map.get(t3, "") tax3_amt = valid_taxes[t3] # ⑫内部银行存款固定100303 inner_bank = "100303" # 内行金额提取:合计行固定为最后一行,J列到W列为合并单元格,数值在J列最后一行 inner_amount = 0.0 last_row = ws.max_row # 最后一行行号 # 取最后一行J列(第10列)的数值,合并单元格的value仅存在于左上角单元格 j_cell_val = ws.cell(row=last_row, column=10).value if isinstance(j_cell_val, (int, float)): inner_amount = j_cell_val row_data = [ row_no, summary, cost_subject, subject_code, round(total_cost, 2), tax1_code, round(tax1_amt, 2), tax2_code, round(tax2_amt, 2), tax3_code, round(tax3_amt, 2), inner_bank, round(inner_amount, 2) ] result_rows.append(row_data) # 写入差旅费汇总表 df_result = pd.DataFrame(result_rows, columns=headers) with pd.ExcelWriter(self.travel_file, engine="openpyxl", mode="w") as writer: df_result.to_excel(writer, sheet_name="差旅费汇总", index=False)# ===================== 程序入口 =====================if __name__ == "__main__": root = tk.Tk() app = ExpenseSummaryGUI(root) root.mainloop()