Kaynağa Gözat

Refactor ExcelConverterApp to handle single file selection and update UI labels. Adjust column mappings and improve employee data processing, including error handling for missing sheets and enhanced logging. Update employee info JSON to remove duplicate company names.

master
lizhuang 2 hafta önce
ebeveyn
işleme
45e0d551c1
3 değiştirilmiş dosya ile 380 ekleme ve 428 silme
  1. 352
    401
      app.py
  2. 20
    19
      config.py
  3. 8
    8
      employee_info.json

+ 352
- 401
app.py Dosyayı Görüntüle

@@ -99,29 +99,29 @@ class ExcelConverterApp:
import_btn.pack(side="left", padx=3)
# 添加文件数量显示
self.file_count_label = ttk.Label(button_frame, text="已选择: 0 个文件")
self.file_count_label = ttk.Label(button_frame, text="已选择: 0 个员工")
self.file_count_label.pack(side="left", padx=10)
# 文件列表区域
list_frame = ttk.Frame(import_frame)
list_frame.pack(fill="both", expand=True)
columns = ("filename", "employee", "company", "bank", "other")
columns = ("employee", "company", "bank", "date", "match_status")
self.file_tree = ttk.Treeview(list_frame, columns=columns, show="headings", selectmode="browse")
# 设置列标题
self.file_tree.heading("filename", text="文件名")
self.file_tree.heading("employee", text="员工姓名 (C3)")
self.file_tree.heading("company", text="所属公司信息 (C2)")
self.file_tree.heading("bank", text="转账银行信息 (B30)")
self.file_tree.heading("other", text="日本日期 (F2)")
self.file_tree.heading("date", text="日本日期 (F2)")
self.file_tree.heading("match_status", text="工作表匹配状态")
# 设置列宽
self.file_tree.column("filename", width=280)
self.file_tree.column("employee", width=110)
self.file_tree.column("employee", width=120)
self.file_tree.column("company", width=170)
self.file_tree.column("bank", width=170)
self.file_tree.column("other", width=120)
self.file_tree.column("date", width=120)
self.file_tree.column("match_status", width=120)
# 添加滚动条
scrollbar_y = ttk.Scrollbar(list_frame, orient="vertical", command=self.file_tree.yview)
@@ -175,13 +175,13 @@ class ExcelConverterApp:
self.status_label.pack(side="left", padx=5)
def select_files(self):
files = filedialog.askopenfilenames(
file = filedialog.askopenfilename(
title="选择Excel文件",
filetypes=[("Excel Files", "*.xlsx")]
)
if files:
self.import_files = list(files)
if file:
self.import_files = [file]
self.update_file_tree()
def update_file_tree(self):
@@ -189,60 +189,73 @@ class ExcelConverterApp:
for item in self.file_tree.get_children():
self.file_tree.delete(item)
# 更新文件计数
self.file_count_label.config(text=f"已选择: {len(self.import_files)} 个文件")
# 用于存储未匹配的员工姓名
unmatch_employees = []
# Add files to tree with employee name and Japanese date
for file in self.import_files:
try:
# Get employee name from C3 cell
filename = os.path.basename(file)
employee_name = ""
original_date = ""
# Get data from Excel file
wb = openpyxl.load_workbook(file)
name_sheet = wb.worksheets[0]
employee_name = name_sheet.cell(row=3, column=3).value or ""
# 获取日期单元格的原始值,不进行格式转换
date_sheet = wb.worksheets[1]
original_date = date_sheet.cell(row=4, column=2).value or ""
# 仅为提取年月信息做最小处理(用于文件名和工作表名称),不影响显示
extracted_year = None
extracted_month = None
# 当前只处理单个文件
if not self.import_files:
return
file = self.import_files[0]
try:
# 打开工作簿
wb = openpyxl.load_workbook(file)
name_sheet = wb.worksheets[0]
# 验证是否存在第二个工作表
if len(wb.worksheets) < 2:
messagebox.showerror("错误", "导入的Excel必须包含至少两个工作表")
self.import_files = []
return
try:
if isinstance(original_date, (int, float)):
# Excel日期数字格式
base_date = datetime(1899, 12, 30)
date_obj = base_date + timedelta(days=original_date)
extracted_year = date_obj.year
extracted_month = date_obj.month
elif isinstance(original_date, datetime):
# 日期对象
extracted_year = original_date.year
extracted_month = original_date.month
else:
# 字符串格式 - 尝试提取年月
date_str = str(original_date)
year_month_match = re.search(r'(\d{4})年(\d{1,2})月', date_str)
if year_month_match:
extracted_year, extracted_month = map(int, year_month_match.groups())
except Exception as e:
logging.error(f"Error processing date: {e}")
date_sheet = wb.worksheets[1]
# 获取日期信息
original_date = date_sheet.cell(row=4, column=2).value or ""
# 提取年月信息
extracted_year = None
extracted_month = None
try:
if isinstance(original_date, (int, float)):
base_date = datetime(1899, 12, 30)
date_obj = base_date + timedelta(days=original_date)
extracted_year = date_obj.year
extracted_month = date_obj.month
elif isinstance(original_date, datetime):
extracted_year = original_date.year
extracted_month = original_date.month
else:
date_str = str(original_date)
year_month_match = re.search(r'(\d{4})年(\d{1,2})月', date_str)
if year_month_match:
extracted_year, extracted_month = map(int, year_month_match.groups())
except Exception as e:
logging.error(f"Error processing date: {e}")
# 生成令和年号日期格式
reiwa_date = ""
if extracted_year and extracted_month:
reiwa_year = extracted_year - 2019 + 1
if reiwa_year > 0:
reiwa_date = f"令和{reiwa_year}年{extracted_month}月分"
# 检查工作表中是否存在员工姓名对应的工作表
sheet_names = wb.sheetnames
employee_sheets = sheet_names[1:] # 从第二个工作表开始
# 获取所有员工数据(从C3开始的行)
current_row = 3 # 从C3开始
while True:
name_cell = name_sheet.cell(row=current_row, column=3) # C列
if not name_cell.value: # 如果C列单元格为空,说明没有更多数据
break
# 生成令和年号日期格式
reiwa_date = ""
if extracted_year and extracted_month:
# 计算令和年号 (2019年为令和元年/1年)
reiwa_year = extracted_year - 2019 + 1
if reiwa_year > 0: # 只有2019年之后才使用令和年号
reiwa_date = f"令和{reiwa_year}年{extracted_month}月分"
employee_name = name_cell.value
if employee_name:
# 清除员工姓名中的所有空格
employee_name = employee_name.replace(" ", "").strip()
name_sheet.cell(row=current_row, column=3).value = employee_name
# 默认值
company = "选择公司"
@@ -252,7 +265,9 @@ class ExcelConverterApp:
employee_matched = False
if employee_name:
for emp_info in EMPLOYEE_INFO:
if emp_info.get("employee_name") == employee_name:
# 清除员工配置中姓名的空格后再比较
config_name = emp_info.get("employee_name", "").replace(" ", "").strip()
if config_name == employee_name:
employee_matched = True
# 匹配到员工信息,更新公司和银行信息
company = emp_info.get("company_name", "")
@@ -261,15 +276,38 @@ class ExcelConverterApp:
bank_info = f"振込先金融機関:{bank_name}\n口座番号:{branch_account}"
break
# 检查员工名称是否在工作表名称中
sheet_match = False
for sheet_name in employee_sheets:
# 清除工作表名称中的空格后再比较
clean_sheet_name = sheet_name.replace(" ", "").strip()
if employee_name == clean_sheet_name:
sheet_match = True
break
# 如果未匹配,添加到未匹配列表
if not employee_matched and employee_name not in unmatch_employees:
unmatch_employees.append(employee_name)
# 自动添加到文件树 - 在日本日期(F2)字段显示令和年月
self.file_tree.insert("", "end", values=(filename, employee_name, company, bank_info, reiwa_date or original_date))
except Exception as e:
logging.error(f"Error reading file {file}: {e}")
self.file_tree.insert("", "end", values=(filename, "", "选择公司", "选择银行", ""))
# 添加到文件树 - 在日本日期(F2)字段显示令和年月及工作表匹配状态
matching_info = "✓ 已匹配工作表" if sheet_match else "✗ 未匹配工作表"
self.file_tree.insert("", "end", values=(
employee_name, # 员工姓名
company, # 公司信息
bank_info, # 银行信息
reiwa_date, # 日期信息
matching_info # 工作表匹配状态
))
current_row += 1
except Exception as e:
logging.error(f"Error reading file {file}: {e}")
# 更新文件计数
total_employees = len(self.file_tree.get_children())
self.file_count_label.config(text=f"已选择: {total_employees} 个员工")
# 如果有未匹配的员工,显示警告消息
if unmatch_employees:
@@ -281,15 +319,15 @@ class ExcelConverterApp:
# 标记未匹配的行
for item in self.file_tree.get_children():
values = self.file_tree.item(item, "values")
if values[1] in unmatch_employees:
if values[0] in unmatch_employees:
self.file_tree.item(item, tags=("unmatch",))
# 设置未匹配样式(黄色背景)
self.file_tree.tag_configure("unmatch", background="#FFF9C4") # 浅黄色
# 设置状态提示
if self.import_files:
self.status_label.config(text="文件已加载,请配置转换选项", foreground=self.text_color)
if total_employees > 0:
self.status_label.config(text="员工信息已加载,请配置转换选项", foreground=self.text_color)
else:
self.status_label.config(text="准备就绪", foreground="gray")
@@ -564,7 +602,7 @@ class ExcelConverterApp:
unmatch_employees = []
for item in self.file_tree.get_children():
values = self.file_tree.item(item, "values")
employee_name = values[1]
employee_name = values[0]
# 检查是否有匹配
if employee_name and not any(emp_info.get("employee_name") == employee_name for emp_info in EMPLOYEE_INFO):
@@ -601,14 +639,10 @@ class ExcelConverterApp:
values = self.file_tree.item(item, "values")
# 安全地获取值
filename = values[0] if len(values) > 0 else ""
employee_name = values[1] if len(values) > 1 else ""
company = values[2] if len(values) > 2 else ""
bank = values[3] if len(values) > 3 else ""
other_info = values[4] if len(values) > 4 else ""
employee_name = values[0]
# Process file
success = self.process_file(file_path, company, bank, other_info, employee_name)
success = self.process_file(file_path, "", "", "", employee_name)
if success:
success_count += 1
# 设置行的背景色为成功
@@ -659,389 +693,306 @@ class ExcelConverterApp:
# Extract name and date
input_wb = openpyxl.load_workbook(input_file)
name_sheet = input_wb.worksheets[0]
name = employee_name or name_sheet.cell(row=3, column=3).value # Use provided name or C3 cell value
date_sheet = input_wb.worksheets[1]
date_value = date_sheet.cell(row=4, column=2).value # B4 cell for date - 只获取不修改
logging.debug(f"Extracted name: {name}")
logging.debug(f"Original date value: {date_value}")
date_value = date_sheet.cell(row=4, column=2).value # B4 cell for date
# 只为了文件名和标题提取年月信息,不修改原始数据
# 提取年月用于文件名和第二个工作表标题
# 获取年月信息
try:
# 使用不同方法尝试获取年月信息,但不修改原始单元格内容
extracted_year = None
extracted_month = None
if isinstance(date_value, (int, float)):
# Excel日期数字格式
base_date = datetime(1899, 12, 30)
date_obj = base_date + timedelta(days=date_value)
extracted_year = date_obj.year
extracted_month = date_obj.month
elif isinstance(date_value, datetime):
# 日期对象
extracted_year = date_value.year
extracted_month = date_value.month
else:
# 字符串格式 - 尝试提取年月
date_str = str(date_value)
# 尝试匹配 YYYY年M月 或 YYYY年M月D日
year_month_match = re.search(r'(\d{4})年(\d{1,2})月', date_str)
if year_month_match:
extracted_year, extracted_month = map(int, year_month_match.groups())
# 如果无法提取年月,使用当前日期
if extracted_year is None or extracted_month is None:
current_date = datetime.now()
extracted_year = current_date.year
extracted_month = current_date.month
logging.warning(f"Could not extract year/month from date value: {date_value}, using current date")
# 用于文件名和工作表标题的年月
year = extracted_year
month = extracted_month
except Exception as e:
# 如果提取失败,使用当前日期
current_date = datetime.now()
year = current_date.year
month = current_date.month
logging.error(f"Error extracting date: {e}, using current date")
logging.debug(f"Using year={year}, month={month} for filename and sheet title")
# Create output filename - 确保使用employee_name
output_filename = OUTPUT_FILENAME_FORMAT.format(year=year, month=f'{month:02}', name=name)
output_path = os.path.join(self.export_dir, output_filename)
logging.debug(f"Output path: {output_path}")
# Copy template file to output
shutil.copy(self.template_path, output_path)
logging.error(f"Error extracting year/month: {e}")
return False
# Open output file
output_wb = openpyxl.load_workbook(output_path)
# 获取所有工作表名称,用于后续匹配
sheet_names = input_wb.sheetnames
# 获取所有员工数据(从C3开始的行)
employee_rows = []
current_row = 3 # 从C3开始
while True:
name_cell = name_sheet.cell(row=current_row, column=3) # C列
if not name_cell.value: # 如果C列单元格为空,说明没有更多数据
break
employee_rows.append(current_row)
current_row += 1
if not employee_rows:
logging.error("No employee data found in the first sheet")
return False
# Apply cell mappings from config
for src, dst in CELL_MAPPINGS.items():
src_sheet_idx, src_row, src_col = src
dst_sheet_idx, dst_row, dst_col = dst
try:
src_sheet = input_wb.worksheets[src_sheet_idx]
# 处理合并单元格问题
cell = src_sheet.cell(row=src_row, column=src_col)
# 如果是合并单元格,获取主单元格的值
if isinstance(cell, openpyxl.cell.cell.MergedCell):
# 找到包含此单元格的合并区域
for merged_range in src_sheet.merged_cells.ranges:
if cell.coordinate in merged_range:
# 获取合并区域左上角单元格的值
top_left = merged_range.min_row, merged_range.min_col
src_value = src_sheet.cell(row=top_left[0], column=top_left[1]).value
break
else:
# 如果没有找到合并区域,设为空值
src_value = None
else:
# 正常单元格直接获取值
src_value = cell.value
dst_sheet = output_wb.worksheets[dst_sheet_idx]
dst_sheet.cell(row=dst_row, column=dst_col).value = src_value
except Exception as e:
# 只记录错误,继续处理下一个单元格
logging.error(f"Error copying cell from {src} to {dst}: {e}")
# 为每个员工创建输出文件
success = True
for row_index in employee_rows:
employee_name = name_sheet.cell(row=row_index, column=3).value
if not employee_name:
continue
# Set custom fields - 使用try/except包裹每个操作
try:
# 尝试查找匹配的员工信息
matched_info = None
for emp_info in EMPLOYEE_INFO:
if emp_info.get("employee_name") == name:
matched_info = emp_info
break
# 如果有匹配的员工信息,则使用员工信息中的公司
if matched_info and matched_info.get("company_name"):
company = matched_info.get("company_name")
sheet_idx, row, col = CUSTOM_CELLS["company"]
output_wb.worksheets[sheet_idx].cell(row=row, column=col).value = company
except Exception as e:
logging.error(f"Error setting company field: {e}")
# 计算转账日期(月份+1)
try:
transfer_month = month + 1
transfer_year = year
if transfer_month > 12:
transfer_month = 1
transfer_year += 1
# 计算令和年号
reiwa_year = transfer_year - 2019 + 1
transfer_date = f"令和{reiwa_year}年{transfer_month}月25日"
# 尝试寻找匹配的银行信息
bank_info = bank
# 清除员工姓名中的所有空格
employee_name = employee_name.replace(" ", "").strip()
# 根据员工姓名查找匹配的员工信息 - 使用EMPLOYEE_INFO
matched_info = None
for emp_info in EMPLOYEE_INFO:
if emp_info.get("employee_name") == name:
matched_info = emp_info
break
if matched_info:
# 使用匹配到的员工信息
bank_name = matched_info.get("bank_name", "")
branch_account = matched_info.get("branch_account", "")
# 使用新的格式
bank_info = f"振込先金融機関:{bank_name}\n口座番号:{branch_account}"
# 组合银行转账信息 - 使用新的格式,名義人始终使用employee_name
if name and transfer_date:
bank_info = f"{bank_info}\n名義人:{name}\n振込日:{transfer_date}\n※休日の場合は、翌営業日にお振込みします。"
sheet_idx, row, col = CUSTOM_CELLS["bank"]
output_wb.worksheets[sheet_idx].cell(row=row, column=col).value = bank_info
# 设置单元格自动换行
cell = output_wb.worksheets[sheet_idx].cell(row=row, column=col)
cell.alignment = Alignment(wrap_text=True, vertical='top')
except Exception as e:
logging.error(f"Error setting bank info: {e}")
try:
sheet_idx, row, col = CUSTOM_CELLS["other"]
# 创建输出文件名
output_filename = OUTPUT_FILENAME_FORMAT.format(
year=year,
month=month,
name=employee_name
)
output_path = os.path.join(self.export_dir, output_filename)
# 生成令和年号日期格式
reiwa_date = ""
if year and month:
# 计算令和年号 (2019年为令和元年/1年)
reiwa_year = year - 2019 + 1
reiwa_date = f"令和{reiwa_year}年{month}月分"
# 加载模板
template_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), TEMPLATE_PATH)
output_wb = openpyxl.load_workbook(template_path)
# 设置F2单元格的值为令和日期格式
output_wb.worksheets[sheet_idx].cell(row=row, column=col).value = reiwa_date or other_info
except Exception as e:
logging.error(f"Error setting other info: {e}")
# Copy second sheet
try:
if len(output_wb.worksheets) == 1:
output_wb.create_sheet()
output_sheet2 = output_wb.worksheets[1]
output_sheet2.title = SHEET2_NAME_FORMAT.format(month=month)
# 应用单元格映射
for src, dst in CELL_MAPPINGS.items():
src_sheet_idx, src_row_offset, src_col = src
dst_sheet_idx, dst_row, dst_col = dst
try:
src_sheet = input_wb.worksheets[src_sheet_idx]
src_row = row_index # 使用当前员工的行
# 处理合并单元格
cell = src_sheet.cell(row=src_row, column=src_col)
if isinstance(cell, openpyxl.cell.cell.MergedCell):
for merged_range in src_sheet.merged_cells.ranges:
if cell.coordinate in merged_range:
top_left = merged_range.min_row, merged_range.min_col
src_value = src_sheet.cell(row=top_left[0], column=top_left[1]).value
break
else:
src_value = None
else:
src_value = cell.value
dst_sheet = output_wb.worksheets[dst_sheet_idx]
dst_sheet.cell(row=dst_row, column=dst_col).value = src_value
except Exception as e:
logging.error(f"Error copying cell from {src} to {dst}: {e}")
continue
# 先保存文件,以便后续使用xlwings处理
output_wb.save(output_path)
# 设置自定义字段
try:
matched_info = None
for emp_info in EMPLOYEE_INFO:
# 清除员工配置中姓名的空格后再比较
config_name = emp_info.get("employee_name", "").replace(" ", "").strip()
if config_name == employee_name:
matched_info = emp_info
break
if matched_info and matched_info.get("company_name"):
company = matched_info.get("company_name")
sheet_idx, row, col = CUSTOM_CELLS["company"]
output_wb.worksheets[sheet_idx].cell(row=row, column=col).value = company
except Exception as e:
logging.error(f"Error setting company field: {e}")
# 尝试使用xlwings做直接复制(对日期格式更可靠)
try:
# 使用更安全的xlwings调用方式
logging.debug("尝试使用xlwings复制第二个工作表...")
sheet_idx, row, col = CUSTOM_CELLS["bank"]
# 计算转账日期(月份+1)
transfer_month = month + 1
transfer_year = year
if transfer_month > 12:
transfer_month = 1
transfer_year += 1
# 使用visible=True可能会解决某些权限问题
with xw.App(visible=False, add_book=False) as app:
# 禁用警告和事件可以提高稳定性
app.display_alerts = False
app.screen_updating = False
# 计算令和年号
reiwa_year = transfer_year - 2019 + 1
transfer_date = f"令和{reiwa_year}年{transfer_month}月25日"
# 获取银行信息
bank_info = bank
if employee_name:
# 查找匹配的员工信息
matched_info = None
for emp_info in EMPLOYEE_INFO:
# 清除员工配置中姓名的空格后再比较
config_name = emp_info.get("employee_name", "").replace(" ", "").strip()
if config_name == employee_name:
matched_info = emp_info
break
try:
# 打开工作簿
input_wb_xw = app.books.open(input_file)
output_wb_xw = app.books.open(output_path)
# 尝试完整复制第二个工作表内容
src_sheet = input_wb_xw.sheets[1]
dst_sheet = output_wb_xw.sheets[1]
if matched_info:
bank_name = matched_info.get("bank_name", "")
branch_account = matched_info.get("branch_account", "")
account_holder = matched_info.get("account_holder", employee_name)
# 复制包含单元格值和格式的范围
src_used = src_sheet.used_range
if src_used:
src_used.copy()
dst_sheet.range('A1').paste()
# 保存和关闭工作簿
output_wb_xw.save()
input_wb_xw.close()
output_wb_xw.close()
logging.debug("使用xlwings成功复制了第二个工作表")
return True # 如果xlwings成功,直接返回成功
except Exception as e:
logging.error(f"xlwings复制过程中发生错误: {e}")
# 关闭所有打开的工作簿
for book in app.books:
try:
book.close()
except:
pass
raise # 重新引发异常,将进入备选方案
except Exception as e:
logging.error(f"使用xlwings复制B4单元格失败: {e},将使用openpyxl备选方案")
# 如果xlwings失败,使用openpyxl作为备选方案继续处理
# 重新加载文件,因为我们之前已保存
output_wb = openpyxl.load_workbook(output_path)
output_sheet2 = output_wb.worksheets[1]
# 组合完整的银行转账信息
bank_info = f"振込先金融機関:{bank_name}\n口座番号:{branch_account}\n名義人:{account_holder}\n振込日:{transfer_date}\n※休日の場合は、翌営業日にお振込みします。"
# 获取源工作表尺寸
max_row = date_sheet.max_row
max_col = date_sheet.max_column
output_wb.worksheets[sheet_idx].cell(row=row, column=col).value = bank_info
# 优先复制所有合并单元格
for merged_range in date_sheet.merged_cells.ranges:
output_sheet2.merge_cells(str(merged_range))
# 设置单元格自动换行
cell = output_wb.worksheets[sheet_idx].cell(row=row, column=col)
cell.alignment = Alignment(wrap_text=True, vertical='top')
except Exception as e:
logging.error(f"Error setting bank info: {e}")
try:
sheet_idx, row, col = CUSTOM_CELLS["other"]
reiwa_date = ""
if year and month:
reiwa_year = year - 2019 + 1
reiwa_date = f"令和{reiwa_year}年{month}月分"
output_wb.worksheets[sheet_idx].cell(row=row, column=col).value = reiwa_date or other_info
except Exception as e:
logging.error(f"Error setting other info: {e}")
# 复制第二个工作表 - 根据员工姓名匹配工作表
try:
if len(output_wb.worksheets) == 1:
output_wb.create_sheet()
output_sheet2 = output_wb.worksheets[1]
output_sheet2.title = SHEET2_NAME_FORMAT.format(month=month)
# 改进的合并单元格处理方式
# 首先识别所有合并单元格的主单元格(左上角单元格)
merged_cell_masters = {}
for merged_range in date_sheet.merged_cells.ranges:
min_row, min_col = merged_range.min_row, merged_range.min_col
merged_cell_masters[(min_row, min_col)] = date_sheet.cell(row=min_row, column=min_col).value
# 查找员工姓名匹配的工作表
matched_sheet = None
for sheet_name in input_wb.sheetnames:
# 清除工作表名称中的空格后再比较
clean_sheet_name = sheet_name.replace(" ", "").strip()
if employee_name == clean_sheet_name:
matched_sheet = sheet_name
break
# 逐个单元格复制
for row in range(1, max_row + 1):
for col in range(1, max_col + 1):
try:
# 获取源单元格
src_cell = date_sheet.cell(row=row, column=col)
dst_cell = output_sheet2.cell(row=row, column=col)
if matched_sheet:
logging.debug(f"Found matching sheet: {matched_sheet} for employee: {employee_name}")
try:
# 保存当前工作簿以便使用xlwings处理
temp_output_path = os.path.join(self.export_dir, f"temp_{employee_name}.xlsx")
output_wb.save(temp_output_path)
# 使用xlwings处理文件 - 这样可以避免合并单元格的问题
with xw.App(visible=False, add_book=False) as app:
app.display_alerts = False
app.screen_updating = False
# 特殊处理B4单元格
if row == 4 and col == 2:
if isinstance(src_cell, openpyxl.cell.cell.MergedCell):
# 如果B4是合并单元格,找到主单元格
for merged_range in date_sheet.merged_cells.ranges:
if src_cell.coordinate in merged_range:
min_row, min_col = merged_range.min_row, merged_range.min_col
main_cell = date_sheet.cell(row=min_row, column=min_col)
dst_cell.value = main_cell.value
# 复制主单元格格式
if hasattr(main_cell, 'number_format'):
dst_cell.number_format = main_cell.number_format
if hasattr(main_cell, 'font'):
dst_cell.font = copy(main_cell.font)
if hasattr(main_cell, 'border'):
dst_cell.border = copy(main_cell.border)
if hasattr(main_cell, 'fill'):
dst_cell.fill = copy(main_cell.fill)
if hasattr(main_cell, 'alignment'):
dst_cell.alignment = copy(main_cell.alignment)
logging.debug(f"B4是合并单元格,已复制主单元格值={main_cell.value}")
break
else:
# 普通单元格处理
dst_cell.value = src_cell.value
# 对于日期类型,特别处理
from datetime import date
if isinstance(src_cell.value, (datetime, date)):
if hasattr(src_cell, 'number_format'):
dst_cell.number_format = src_cell.number_format
else:
# 设置通用日期格式
dst_cell.number_format = "yyyy/mm/dd"
# 复制格式属性
if hasattr(src_cell, 'font'):
dst_cell.font = copy(src_cell.font)
if hasattr(src_cell, 'border'):
dst_cell.border = copy(src_cell.border)
if hasattr(src_cell, 'fill'):
dst_cell.fill = copy(src_cell.fill)
if hasattr(src_cell, 'alignment'):
dst_cell.alignment = copy(src_cell.alignment)
if hasattr(src_cell, 'number_format'):
dst_cell.number_format = src_cell.number_format
try:
# 打开输入和输出文件
input_wb_xw = app.books.open(input_file)
output_wb_xw = app.books.open(temp_output_path)
# 获取匹配的工作表
src_sheet = None
for sheet in input_wb_xw.sheets:
if sheet.name == matched_sheet:
src_sheet = sheet
break
if src_sheet:
# 获取输出工作表
dst_sheet = output_wb_xw.sheets[1]
# 复制所有单元格的值 - 只复制数据
used_range = src_sheet.used_range
data_values = used_range.value # 获取所有值而不带格式
logging.debug(f"B4是普通单元格,已复制值={src_cell.value}, 类型={type(src_cell.value)}")
else:
# 一般单元格处理 - 区分合并单元格与普通单元格
if isinstance(src_cell, openpyxl.cell.cell.MergedCell):
# 对于合并单元格中的从属单元格,值保持为None
dst_cell.value = None
else:
# 普通单元格的值直接复制
dst_cell.value = src_cell.value
# 确保data_values不为None
if data_values:
# 确保是二维数组
if not isinstance(data_values, list):
data_values = [[data_values]]
elif not isinstance(data_values[0], list):
data_values = [data_values]
# 复制格式
if hasattr(src_cell, 'font'):
dst_cell.font = copy(src_cell.font)
if hasattr(src_cell, 'border'):
dst_cell.border = copy(src_cell.border)
if hasattr(src_cell, 'fill'):
dst_cell.fill = copy(src_cell.fill)
if hasattr(src_cell, 'alignment'):
dst_cell.alignment = copy(src_cell.alignment)
if hasattr(src_cell, 'number_format'):
dst_cell.number_format = src_cell.number_format
continue
# 处理合并单元格
if isinstance(src_cell, openpyxl.cell.cell.MergedCell):
# 对于合并单元格,找到合并区域的左上角主单元格的值
cell_value = None
for merged_range in date_sheet.merged_cells.ranges:
if src_cell.coordinate in merged_range:
top_left = merged_range.min_row, merged_range.min_col
cell_value = date_sheet.cell(row=top_left[0], column=top_left[1]).value
break
else:
# 普通单元格直接读取值
cell_value = src_cell.value
# 设置目标单元格值
output_sheet2.cell(row=row, column=col).value = cell_value
# 复制单元格格式(如果源单元格不是合并单元格)
if not isinstance(src_cell, openpyxl.cell.cell.MergedCell):
if hasattr(src_cell, 'has_style') and src_cell.has_style:
output_cell = output_sheet2.cell(row=row, column=col)
# 写入所有值
dst_sheet.range('A1').value = data_values
# 保存并关闭
output_wb_xw.save()
input_wb_xw.close()
output_wb_xw.close()
# 重新加载保存的文件
output_wb = openpyxl.load_workbook(temp_output_path)
# 删除临时文件
if os.path.exists(temp_output_path):
try:
output_cell.font = copy(src_cell.font)
output_cell.border = copy(src_cell.border)
output_cell.fill = copy(src_cell.fill)
output_cell.number_format = src_cell.number_format
output_cell.protection = copy(src_cell.protection)
output_cell.alignment = copy(src_cell.alignment)
os.remove(temp_output_path)
except:
# 忽略样式复制错误
pass
logging.debug(f"Successfully copied data from sheet {matched_sheet} using xlwings")
except Exception as e:
logging.error(f"Error in xlwings processing: {e}")
# 如果xlwings失败,我们将尝试直接处理数据
try:
# 重新打开工作簿
output_wb = openpyxl.load_workbook(template_path)
if len(output_wb.worksheets) == 1:
output_wb.create_sheet()
output_sheet2 = output_wb.worksheets[1]
output_sheet2.title = SHEET2_NAME_FORMAT.format(month=month)
# 直接使用openpyxl
src_sheet = input_wb[matched_sheet]
# 获取所有单元格的值并只复制值
max_row = src_sheet.max_row
max_col = src_sheet.max_column
for r in range(1, max_row + 1):
for c in range(1, max_col + 1):
src_cell = src_sheet.cell(row=r, column=c)
# 判断是否为合并单元格
if isinstance(src_cell, openpyxl.cell.cell.MergedCell):
# 对于合并单元格,跳过写入
continue
else:
# 对于普通单元格,只复制值
output_sheet2.cell(row=r, column=c).value = src_cell.value
logging.debug(f"Successfully copied data from sheet {matched_sheet} using openpyxl fallback")
except Exception as fallback_error:
logging.error(f"Fallback error: {fallback_error}")
raise
except Exception as e:
logging.error(f"Error copying sheet {matched_sheet} data: {e}")
success = False
else:
logging.warning(f"No matching sheet found for employee: {employee_name}")
except Exception as e:
# 更加详细的错误记录
logging.warning(f"复制单元格 row={row}, col={col} 时出错: {e}", exc_info=True)
continue
except Exception as e:
logging.error(f"Error setting up second sheet: {e}")
success = False
# 保存输出工作簿
try:
output_wb.save(output_path)
logging.debug(f"File processed successfully for employee: {employee_name}")
except Exception as e:
logging.error(f"复制第二个工作表时出错: {e}", exc_info=True)
return False
except Exception as e:
logging.error(f"Error copying second sheet: {e}")
logging.error(f"Error saving output file: {e}")
success = False
# Save output workbook
try:
output_wb.save(output_path)
logging.debug("File processed successfully")
return True
except Exception as e:
logging.error(f"Error saving output file: {e}")
return False
return success
except Exception as e:
logging.error(f"Error processing file: {e}")
# 不显示消息框,只记录错误
return False



+ 20
- 19
config.py Dosyayı Görüntüle

@@ -13,27 +13,28 @@ BANK_CONFIG_PATH = os.path.join(CONFIG_DIR, "bank_options.json") # 保留兼容

# 单元格映射关系(源文件位置 -> 目标文件位置)
# 格式:{(源文件sheet索引, 行, 列): (目标文件sheet索引, 行, 列)}
# 注意:源文件行索引为0表示从C3开始的行
CELL_MAPPINGS = {
# 第一个Sheet的映射关系
(0, 3, 3): (0, 2, 5), # C3 -> E2 氏名
(0, 3, 4): (0, 3, 5), # D3 -> E3 労働日数
(0, 3, 2): (0, 3, 3), # B3 -> C3 ID
(0, 3, 5): (0, 6, 4), # E3 -> D6 基本給
(0, 3, 6): (0, 7, 4), # F3 -> D7 職務給
(0, 3, 7): (0, 8, 4), # G3 -> D8 資格手当
(0, 3, 8): (0, 9, 4), # H3 -> D9 住宅手当
(0, 3, 9): (0, 10, 4), # I3 -> D10 能力手当
(0, 3, 10): (0, 11, 4), # J3 -> D11 通勤手当
(0, 3, 11): (0, 12, 4), # K3 -> D12 残業(固定)
(0, 3, 12): (0, 13, 4), # L3 -> D13
(0, 3, 13): (0, 14, 4), # M3 -> D14
(0, 3, 15): (0, 17, 4), # O3 -> D17
(0, 3, 16): (0, 18, 4), # P3 -> D18
(0, 3, 17): (0, 19, 4), # Q3 -> D19
(0, 3, 18): (0, 20, 4), # R3 -> D20
(0, 3, 19): (0, 21, 4), # S3 -> D21
(0, 3, 20): (0, 22, 4), # T3 -> D22
(0, 3, 25): (0, 25, 5), # Y3 -> E25
(0, 0, 3): (0, 2, 5), # C3 -> E2 氏名
(0, 0, 4): (0, 3, 5), # D3 -> E3 労働日数
(0, 0, 2): (0, 3, 3), # B3 -> C3 ID
(0, 0, 5): (0, 6, 4), # E3 -> D6 基本給
(0, 0, 6): (0, 7, 4), # F3 -> D7 職務給
(0, 0, 7): (0, 8, 4), # G3 -> D8 資格手当
(0, 0, 8): (0, 9, 4), # H3 -> D9 住宅手当
(0, 0, 9): (0, 10, 4), # I3 -> D10 能力手当
(0, 0, 10): (0, 11, 4), # J3 -> D11 通勤手当
(0, 0, 11): (0, 12, 4), # K3 -> D12 残業(固定)
(0, 0, 12): (0, 13, 4), # L3 -> D13 週末・祝日出勤手当
(0, 0, 13): (0, 14, 4), # M3 -> D14 時間外EC操作
(0, 0, 15): (0, 17, 4), # O3 -> D17
(0, 0, 16): (0, 18, 4), # P3 -> D18
(0, 0, 17): (0, 19, 4), # Q3 -> D19
(0, 0, 18): (0, 20, 4), # R3 -> D20
(0, 0, 19): (0, 21, 4), # S3 -> D21
(0, 0, 20): (0, 22, 4), # T3 -> D22
(0, 0, 25): (0, 25, 5), # Y3 -> E25
}

# 自定义值的单元格位置

+ 8
- 8
employee_info.json Dosyayı Görüntüle

@@ -1,10 +1,10 @@
[
{
"employee_name": "杜云",
"company_name": "SPD株式会社",
"bank_name": "ゆうちょ銀行",
"branch_account": "一三八(普通)2192640",
"account_holder": "杜云 ト ウン",
"company_name": "SPD株式会社"
"account_holder": "杜云 ト ウン"
},
{
"employee_name": "範慶博",
@@ -29,10 +29,10 @@
},
{
"employee_name": "欧阳冠英",
"company_name": "SPD株式会社",
"bank_name": "楽天銀行",
"branch_account": "〇五八(普通)9357044",
"account_holder": "欧阳 冠英 オウヤン グアンイン",
"company_name": "SPD株式会社"
"account_holder": "欧阳 冠英 オウヤン グアンイン"
},
{
"employee_name": "孙文岱",
@@ -43,17 +43,17 @@
},
{
"employee_name": "王磊",
"company_name": "SPD株式会社",
"bank_name": "三井住友銀行",
"branch_account": "町屋支店(228)(普通)6919966",
"account_holder": "王磊 オウ ライ",
"company_name": "SPD株式会社"
"account_holder": "王磊 オウ ライ"
},
{
"employee_name": "魏强",
"company_name": "SPD株式会社",
"bank_name": "ゆうちょ銀行",
"branch_account": "〇一八(普通)8654986",
"account_holder": "魏强 ギ キョウ",
"company_name": "SPD株式会社"
"account_holder": "魏强 ギ キョウ"
},
{
"employee_name": "吴桐宇",

Loading…
İptal
Kaydet