发票PDF智能合并工具。多张发票+行程单自动配对拼版到A4纸(2合1),保持文字可选中,落单发票自动两两合并省纸,自动删除空白尾页。纯排版工具,不含数据提取。
---
name: invoice-pdf-merge
slug: invoice-pdf-merge
displayName: 发票PDF智能合并
description: 发票PDF智能合并工具。多张发票+行程单自动配对拼版到A4纸(2合1),保持文字可选中,落单发票自动两两合并省纸,自动删除空白尾页。纯排版工具,不含数据提取。
version: 1.2.0
license: MIT
---
# 发票 PDF 智能合并
将多张发票 PDF 拼版到同一张 A4 纸上,节省打印纸张,同时保持 PDF 文字可选中特性。
## 核心特性
- **保持文字可选中** — 使用 PyMuPDF 的 `show_pdf_page()`,不转图片
- **发票上 + 行程单下** — 固定上下布局,发票自适应缩放至半页,行程单按原始尺寸铺排
- **自动删除空白尾页** — 检测并删除仅有页码的无意义尾页
- **落单发票处理** — 只占上半张 A4,下半留空,大小与合并时一致
- **批量处理** — 自动扫描文件夹,按文件名智能配对发票和行程单
## 安装依赖
```bash
/usr/bin/python3 -m pip install PyMuPDF --user
```
> **注意**:必须使用系统 Python 安装,WorkBuddy 管理的 Python 环境可能不同。
## 快速使用
**核心设计原则:一张发票对应一个行程,按文件名中的共同前缀智能配对。**
### 方式一:批量处理文件夹
将脚本放在待处理文件夹同级目录,然后运行:
```bash
/usr/bin/python3 merge_invoices.py <输入文件夹> <输出文件夹>
```
脚本会自动:
1. **提取分组标识** — 从文件名中【】括号内的文字提取(如 `【打车服务-2024年6月-市区到机场】`)
2. **同一标识下的文件归为一组**(每组 = 1个独立行程)
3. 组内精确匹配「电子发票/电子普通发票」+「电子行程单/行程报销单」(用完整后缀判断,避免关键词误匹配)
4. 每组输出一个 PDF:发票在上 + 行程单在下
5. 无法配对的单独处理(只占上半张 A4)
### 方式二:合并两个指定 PDF
```bash
/usr/bin/python3 merge_invoices.py invoice1.pdf invoice2.pdf output.pdf
```
## 脚本说明
核心脚本:`scripts/merge_invoices.py`
### 主要函数
| 函数 | 用途 |
|------|------|
| `merge_invoice_itinerary()` | 发票+行程单上下布局合并,自动删空白尾页 |
| `merge_two_pdfs()` | 两个PDF简单2合1拼版(上下或左右) |
| `place_single_invoice()` | 落单发票放到上半张A4 |
| `get_content_bottom()` | 检测页面实际内容底部坐标 |
| `is_trivial_page()` | 判断页面是否仅有页码等无意义内容 |
| `auto_merge_folder()` | 批量扫描文件夹,按【】前缀智能分组配对 |
| `_extract_group_key()` | 从文件名提取分组标识(【】括号或去后缀) |
> **注意**:v1.1.0 起移除了内置 Excel 生成功能(`extract_invoice_info()`、`generate_excel()` 和 `--excel` 参数)。数据提取和 Excel 生成由 `invoice-from-email` 技能负责。
### 关键设计决策
1. **固定上下布局** — 发票在上半张、行程单在下半张,符合阅读习惯
2. **行程单不压缩** — 按原始尺寸铺排,内容完整可读
3. **检测内容区域** — 用 `page.get_text("dict")["blocks"]` 获取 bbox,而非依赖页面尺寸
4. **删除废页** — 输出后检查最后一页,若仅含页码(支持 `"页码:1/1"`、`"页码: 1 / 1"` 等带空格格式)则删除
## 版本历史
### v1.2.0 (2026-07-14)
- **新功能**:`auto_merge_folder()` 自动将落单发票拼版合并(2 张/页 A4),大幅节省纸张
- **落单逻辑**:所有落单票先各自放到半页 A4 → 再两两合并到一页 → 最后一张落单保留半页
### v1.1.0 (2026-07-14)
- **重构**:移除 `merge_invoices.py` 内置的 Excel 生成功能(`extract_invoice_info()`、`generate_excel()` 和 `--excel` 参数)
- **职责分离**:本技能回归纯排版(PDF 合并),数据提取和 Excel 生成交给 `invoice-from-email` 技能负责
### v1.0.1 (2026-06-13)
- **Bug 修复**:`merge_invoices.py` 第545行命令行参数名错误 — `generate_excel` 改为 `gen_excel`,修复 `--generate-excel` 报错 `unrecognized arguments`
- **触发场景**:运行 `auto_merge_folder` 时传入 `--generate-excel` 参数会触发此 bug
## 常见问题
**Q:行程单内容太多,合并后变成多页?**
A:这是预期行为。行程单按原始尺寸铺排,超出部分自动续页。如果不需要多页,可以修改脚本改用左右布局。
**Q:合并后的PDF文字不可选?**
A:检查是否安装了正确版本的 PyMuPDF。必须使用 `show_pdf_page()` 方法,不能将 PDF 转为图片再合并。
**Q:落单发票太大/太小?**
A:落单发票会使用与合并时相同的缩放逻辑,放到上半张A4的中心位置,大小一致。
## 输出文件命名
- 每组配对输出一个独立 PDF:`{分组标识前30字}_合并.pdf`(如 `打车服务-2024年6月_合并.pdf`)
- 落单发票:保持原文件名,只占上半张 A4
## 配对逻辑详解
旧版(已废弃)用关键词 `"发票" in f` / `"行程" in f` 匹配,但发票文件名常含「1个行程」等文字导致**误匹配**。
新版采用两步法:
1. **先分组**:提取文件名中【】括号内的文字作为分组 key
2. **再精确匹配**:组内用完整后缀(`.电子发票.pdf` / `.电子行程单.pdf`)判断类型
don't have the plugin yet? install it then click "run inline in claude" again.