今天给大家分享一个开箱即用的Python工具:自动读取Excel单据,按单价将大额记录拆分为多行,每行金额尽量接近阈值且不超限,输出结果自带专业格式美化,十几秒就能搞定原本大半天的手工活。
做财务、开票或者供应链的朋友,大概率都遇到过这个经典痛点:
公司规定单笔单据、发票金额不能超过10000元,但业务数据里经常出现一笔几十件、总金额数万的记录。
手动拆分?算每行数量、核对金额,几十行原始数据能拆出上百行明细,既耗时间又容易算错,反复核对更是折磨人。
一、拆分规则
先明确通用的拆分逻辑,阈值可根据自身需求调整:
- 单笔总金额 ≤ 10000元:不拆分,原样保留
- 单笔总金额 > 10000元:按固定单价拆分为多行
- 每行金额尽可能接近10000元(不超过阈值),最后一行放置剩余数量
- 拆分前后总数量、总金额完全一致,数据零误差
二、核心实现拆解
整个工具分为三层:拆分算法、数据批量处理、Excel格式美化。
1. 核心拆分算法
拆分的本质是一道基础算术题:已知总数量、单价、金额上限,求单行最大数量与拆分行数。
核心逻辑步骤:
- 计算单笔总金额,未超过阈值直接返回原数据
- 用「阈值 ÷ 单价」向下取整,得到单行可放置的最大数量
- 总数量除以单行最大数量,得到完整行数与剩余数量
- 拼接完整行与余数行,形成最终拆分结果
额外处理了边界场景:若单件单价本身高于阈值,无法拆分则直接保留原行。
2. Excel批量读写
基于pandas实现全量数据处理:
- 读取原始Excel表格,支持指定工作表
- 逐行调用拆分算法,生成明细行
- 保留原始日期、名称、单价等字段,仅更新数量与金额
- 自动兼容文件已存在、不存在两种场景,避免重复运行报错
3. 自动格式美化
很多脚本导出的Excel都是无格式的“裸数据”,还需要手动排版。这个工具直接通过openpyxl完成美化:
- 深色表头搭配白色加粗字体,层级清晰
- 全表单元格居中对齐,统一细边框
- 预设合理列宽,无需手动调整
- 冻结首行,滚动查看数据时表头始终可见
导出的结果文件可直接用于汇报、发同事,无需二次加工。
三、完整代码与使用方法
1. 安装依赖
运行前先安装所需第三方库:
pipinstallpandas openpyxl2. 配置说明
修改代码开头CONFIG字典即可适配你的表格:
input_path:原始Excel文件路径output_path:拆分结果输出路径- 各
*_col:对应你表格中的列名 threshold:拆分金额阈值,默认10000,可自由调整
3. 完整可运行代码
#!/usr/bin/env python3# -*- coding: utf-8 -*-""" 金额按单价拆分工具 规则:金额 ≥ 阈值时,按单价拆分为多行, 每行金额尽量接近阈值(不超过),最后一行是余数。 """importmathimportosimportpandasaspdfromopenpyxlimportload_workbookfromopenpyxl.stylesimportFont,Alignment,PatternFill,Border,Side# ===================== 配置区 =====================CONFIG={"input_path":"sample.xlsx","output_path":"result.xlsx","date_col":"日期","name_col":"名称","qty_col":"数量","price_col":"单价","amount_col":"金额","threshold":10000,# 大于此金额才拆分"sheet_name":0,# 读取第几个工作表,从0开始}# ==================================================defsplit_row(qty,price,threshold=10000):""" 把 (数量, 单价) 拆成 [(数量, 金额), ...] 规则:每行金额尽量接近 threshold(不超过),最后一行是余数 """total_amount=qty*price# 不超过阈值 → 不拆iftotal_amount<=threshold:return[(qty,round(total_amount,2))]# 找每行最大数量:floor(threshold / price)per_qty=math.floor(threshold/price)# 如果单价 > 阈值,单行就超了 → 只能拆成1行ifper_qty<1:return[(qty,round(total_amount,2))]# 完整份数 + 余数full_parts=qty//per_qty remainder=qty%per_qty result=[]for_inrange(int(full_parts)):result.append((per_qty,round(per_qty*price,2)))ifremainder>0:result.append((int(remainder),round(remainder*price,2)))returnresultdefprocess(input_path,date_col,name_col,qty_col,price_col,amount_col,threshold=10000,sheet_name=0):"""读取Excel并逐行拆分"""df=pd.read_excel(input_path,sheet_name=sheet_name)rows=[]for_,rindf.iterrows():qty=r[qty_col]price=r[price_col]splits=split_row(qty,price,threshold)fornew_qty,new_amountinsplits:rows.append({date_col:r[date_col],name_col:r[name_col],qty_col:new_qty,price_col:price,amount_col:new_amount,})returnpd.DataFrame(rows)defsave(result_df,output_path,cols):"""保存结果并美化格式"""ifnotos.path.exists(output_path):withpd.ExcelWriter(output_path,engine="openpyxl")aswriter:result_df.to_excel(writer,sheet_name="拆分结果",index=False)else:withpd.ExcelWriter(output_path,engine="openpyxl",mode="a",if_sheet_exists="replace")aswriter:result_df.to_excel(writer,sheet_name="拆分结果",index=False)_beautify(output_path,"拆分结果",cols)def_beautify(file_path,sheet_name,cols):"""Excel样式美化"""wb=load_workbook(file_path)ws=wb[sheet_name]# 表头样式header_font=Font(name="微软雅黑",size=11,bold=True,color="FFFFFF")header_fill=PatternFill(start_color="2C3E50",end_color="2C3E50",fill_type="solid")thin_border=Border(left=Side(style="thin"),right=Side(style="thin"),top=Side(style="thin"),bottom=Side(style="thin"))center_align=Alignment(horizontal="center",vertical="center")# 应用表头样式forcolinrange(1,ws.max_column+1):cell=ws.cell(row=1,column=col)cell.font=header_font cell.fill=header_fill cell.alignment=center_align cell.border=thin_border# 应用内容样式forrinrange(2,ws.max_row+1):forcinrange(1,ws.max_column+1):cell=ws.cell(row=r,column=c)cell.alignment=center_align cell.border=thin_border# 预设列宽widths={"A":14,"B":20,"C":10,"D":10,"E":12}forcol_letter,winwidths.items():ws.column_dimensions[col_letter].width=w ws.freeze_panes="A2"wb.save(file_path)if__name__=="__main__":print("="*60)print(" 金额拆分工具")print("="*60)print(f" 输入文件:{CONFIG['input_path']}")print(f" 拆分阈值:{CONFIG['threshold']}元")print()cols=[CONFIG["date_col"],CONFIG["name_col"],CONFIG["qty_col"],CONFIG["price_col"],CONFIG["amount_col"]]# 执行拆分result=process(CONFIG["input_path"],CONFIG["date_col"],CONFIG["name_col"],CONFIG["qty_col"],CONFIG["price_col"],CONFIG["amount_col"],CONFIG["threshold"],CONFIG["sheet_name"],)# 保存结果save(result,CONFIG["output_path"],cols)# 校验结果threshold=CONFIG["threshold"]max_amt=result[CONFIG["amount_col"]].max()print(f" 原始行数 → 拆分后{len(result)}行")print(f" 最大单行金额:{max_amt}元 (阈值{threshold}元)")status="全部符合阈值要求"ifmax_amt<=thresholdelse"存在超阈值行"print(f" 校验状态:{status}")print()# 按名称分组打印明细print(" 拆分详情:")forname,subinresult.groupby(CONFIG["name_col"]):total_amt=sub[CONFIG["amount_col"]].sum()total_qty=sub[CONFIG["qty_col"]].sum()rows=len(sub)print(f"{name}:{rows}行,{int(total_qty)}件, 合计{total_amt:.2f}元")for_,rinsub.iterrows():print(f" └─{r[CONFIG['qty_col']]:>4}×{r[CONFIG['price_col']]:>6.2f}={r[CONFIG['amount_col']]:>8.2f}")print(f"\n结果已写入:{CONFIG['output_path']}→「拆分结果」工作表")四、运行效果
脚本运行后,控制台会输出完整的校验信息:
- 拆分前后行数对比
- 最大单行金额校验,确认是否全部符合阈值要求
- 按商品分组的明细清单,每行数量、单价、金额清晰展示
相当于自动完成了数据核对,不用再手动求和校验总数。
这个工具逻辑不复杂,但解决的是非常高频的办公痛点。把重复、机械的拆分核对工作交给代码,既能提升效率,也能避免人工计算的失误。
你可以根据自己的业务需求继续扩展:比如支持多sheet批量处理、增加汇总统计、对接开票系统直接生成发票明细等等。