返回 Skill 列表
extension
分类: 其它无需 API Key

invoice-organizer

中国电子发票与行程单批量整理工具。扫描指定文件夹的PDF/OFD文件,自动识别文档类型(发票/行程单/结账单),提取关键字段,生成Excel统计汇总表(含明细+按月/季度/年度/费用类别汇总)。当用户需要整理发票、统计报销、查看发票明细时使用此skill。

person作者: user_79704987hubcommunity

Invoice Organizer - 发票整理技能

概述

中国电子发票与行程单批量整理工具。自动识别文档类型、提取关键信息、生成统计表格。

核心能力:

  • 🔍 自动识别文档类型:发票 vs 行程单 vs 结账单
  • 📦 ZIP压缩包支持:自动解压并检索压缩包内的PDF/OFD发票
  • 🔄 重复发票检测:基于发票号码自动识别重复,标红高亮
  • 📊 批量提取发票字段:号码、日期、买卖方、项目、金额、税率、税额、价税合计
  • ✈️ 支持机票报销凭证:提取航班号、起降地等附加信息
  • 🚗 识别网约车行程单:提取平台、行程日期、金额(标注为非发票)
  • 📈 多维度统计汇总:按月/季度/年度/费用类别
  • 📂 智能输出路径:根据整理范围自动保存到对应文件夹
  • 🏷️ 报销类别自动分类(按月整理时):按销售方和项目内容自动分类到 综合费用(招待费/办公费/行政后勤/采购垫付/物流垫付/其它)和 差旅费用(住宿/交通/油补/餐补/通讯/其它),再按公司主体分子文件夹,并移动到对应目录(重名时覆盖)
  • 🤝 两阶段交互式分类确认:无法自动识别的发票,分两阶段让用户确认——第一阶段选择大类别,第二阶段选择子类

目录结构:按类别整理/大类/子类/公司主体/发票.pdf

⚠️ 重要区分:行程单不是发票!

  • 网约车行程单(享道/滴滴/高德/曹操等)= 运输记录凭证,非发票
  • 机票报销凭证 = 是发票(项目名称为"经纪代理服务机票款")
  • 结账单/账单 = 非发票

核心规则(必须遵守)

⚠️ 按月整理的三条核心规则:

  1. 寻找月份文件夹:按月份整理 = 找到命名为月份的文件夹(如 20260331),只整理该文件夹下的发票
  2. 先去重再确认:先去重(同一发票号码只保留一张),去重后的待确认发票才需要交互确认
  3. 两阶段确认:无法自动识别的发票,先确认大类别(综合费用/差旅费用),再确认子类

使用场景

| 场景 | 用户指令示例 | CLI参数 | 输出位置 | |------|-------------|---------|----------| | 指定文件夹 | "整理这个文件夹下的发票" | input_dir=子文件夹路径 | 子文件夹下 | | 按年整理 | "帮我整理2025年的发票" | --year 2025 | 2025年/发票统计汇总表_2025.xlsx | | 按月整理 | "帮我整理2026年3月发票" | --month 2026-03 | 2026年/20260331/发票统计汇总表_2026-03.xlsx | | 交互选择 | "帮我整理发票" | 无参数 | 提示选择 → 根据选择 | | 全量整理 | "整理所有发票" | 仅 input_dir | 发票统计汇总表.xlsx |

工作流程

第一步:确认范围

询问用户或从上下文确定:

  1. 目标目录:包含发票文件的文件夹路径
  2. 处理范围
    • 指定文件夹 → 全量扫描该文件夹
    • 指定年份 → --year 2025
    • 指定月份 → --month 2026-03(推荐日常报销)
    • 不确定 → 运行 --list 让用户选择
  3. 输出位置:根据范围自动确定(见上表)

第二步:扫描文件

使用 scripts/invoice_extractor.py 扫描目标目录:

按月整理(推荐日常报销):

# 支持 2026-03, 202603, 2026年3月, 2026年03月 等格式
# ⚠️ 直接定位到月份文件夹(如20260331),只扫描该文件夹下的发票
# ⚠️ 分类结果也放在月份文件夹下的"按类别整理/"目录
python ~/.workbuddy/skills/invoice-organizer/scripts/invoice_extractor.py "<目标目录>" --month 2026-03
# 输出: <目标目录>/2026年/20260331/发票统计汇总表_2026-03.xlsx
# 分类: <目标目录>/2026年/20260331/按类别整理/

按年整理:

python ~/.workbuddy/skills/invoice-organizer/scripts/invoice_extractor.py "<目标目录>" --year 2025
# 输出: <目标目录>/2025年/发票统计汇总表_2025.xlsx

指定文件夹(全量):

# 直接指定子文件夹
python ~/.workbuddy/skills/invoice-organizer/scripts/invoice_extractor.py "<目标目录>/2024年/发票-20241031"
# 输出: <目标目录>/2024年/发票-20241031/发票统计汇总表.xlsx

全量整理:

python ~/.workbuddy/skills/invoice-organizer/scripts/invoice_extractor.py "<目标目录>"
# 输出: <目标目录>/发票统计汇总表.xlsx

查看可用年份/月份:

python ~/.workbuddy/skills/invoice-organizer/scripts/invoice_extractor.py "<目标目录>" --list

其他参数:

# 自定义输出路径
python invoice_extractor.py "<目标目录>" -o "<输出路径>.xlsx"

# 同时输出JSON(便于程序处理)
python invoice_extractor.py "<目标目录>" --json

# 禁用缓存,强制全量提取
python invoice_extractor.py "<目标目录>" --no-cache

月份文件夹名识别

脚本支持多种月份文件夹命名格式,自动匹配对应月份:

| 文件夹名 | 匹配月份 | 说明 | |----------|----------|------| | 01月 / 1月 / 01 | 当年该月 | 需在年份文件夹下 | | 2026年4月 / 2026年04月 | 2026年4月 | 文件夹名含年份 | | 202604 | 2026年4月 | 6位纯数字=年月 | | 2026-04 / 2026_04 | 2026年4月 | 分隔符格式 | | 20260331 | 2026年3月 | 8位日期格式,自动提取年月 | | 发票-20240301 | 2024年3月 | 带前缀8位日期 | | 发票-20240430 | 2024年4月 | 同上 |

输出路径规则

| 整理范围 | 输出文件路径 | 示例 | |----------|-------------|------| | 全量 | <根目录>/发票统计汇总表.xlsx | 报销发票/发票统计汇总表.xlsx | | 按年 | <根目录>/<年>年/发票统计汇总表_<年>.xlsx | 报销发票/2025年/发票统计汇总表_2025.xlsx | | 按月 | <根目录>/<年>年/<月文件夹>/发票统计汇总表_<年>-<月>.xlsx | 报销发票/2026年/20260331/发票统计汇总表_2026-03.xlsx | | 指定文件夹 | <子文件夹>/发票统计汇总表.xlsx | 发票-20241031/发票统计汇总表.xlsx |

设计原则:生成的表格和发票文件放在一起,便于对照查看和提交报销。

也可以直接在Python中调用:

from invoice_extractor import scan_directory, process_single_file, create_output_workbook, _detect_duplicates
# 按月扫描(含ZIP内文件)
file_entries = scan_directory(base_dir, month_filter=(2026, 3))
# file_entries 是 dict list: [{'filepath': ..., 'source_zip': None/..., 'zip_inner_path': None/...}]
results = [process_single_file(e['filepath']) for e in file_entries]
duplicate_groups = _detect_duplicates(results)
wb = create_output_workbook(results, base_dir, duplicate_groups=duplicate_groups)
wb.save(output_path)

第三步:审查结果

检查提取结果的准确性:

  • 确认文档分类是否正确(发票/行程单/结账单)
  • 检查关键字段是否完整(发票号码、日期、金额)
  • 核对价税合计(必须从发票原文提取,不要自行计算)
  • 标注提取不完整的记录

第四步:补充修正

对提取不完整的记录进行手动修正:

  • 旧版增值税电子发票(印章覆盖格式):购买方/销售方可能缺失
  • OFD文件:已支持解析,自动识别三种内部XML结构
  • 扫描件PDF:文本无法提取

第五步:交付成果

生成的Excel包含以下Sheet:

| Sheet | 内容 | 说明 | |-------|------|------| | 发票明细 | 所有发票的详细记录 | 含"是否重复"和"来源"列,重复项标红 | | 行程单明细 | 网约车等行程单记录 | 标注为"非发票",含重复检测 | | 非发票文件 | 结账单、未识别文件 | 备注说明原因 | | 重复发票 | 重复发票/行程单汇总 | 仅在有重复时出现,首次出现标黄、重复标红 | | 按月汇总 | 月度统计 | 数量+金额+税额 | | 按季度汇总 | 季度统计 | 数量+金额+税额 | | 按年度汇总 | 年度统计 | 数量+金额+税额 | | 按费用类别汇总 | 按项目类别统计 | 含占比 |

支持的发票类型

详见 references/invoice_types.md

| 类型 | 识别方式 | 提取难度 | |------|---------|---------| | 全电普通发票 | PDF文本 "电子发票(普通发票)" | ★☆☆ | | 全电增值税专用发票 | PDF文本 "电子发票(增值税专用发票)" | ★☆☆ | | 增值税电子普通发票 | PDF文本 "增值税电子普通发票" | ★★☆ | | 机票报销凭证 | 文件名含"机票"+"报销凭证" | ★★★ | | 网约车行程单 | 文件名含"行程单"/"出行" | ★★☆ (非发票) | | OFD发票 | 文件扩展名 .ofd,解析内部XML | ★★☆ |

注意事项

  1. 数据以发票原文为准:价税合计等字段必须从发票PDF/OFD原文直接提取,禁止通过金额+税额自行计算
  2. 区分发票和行程单:行程单不是发票,在统计表格中要单独一个Sheet
  3. 提取失败处理:对无法提取的文件(扫描件),在表格中标注原因
  4. 金额精度:所有金额保留2位小数,使用Excel数字格式
  5. 表格位置:生成的Excel放在发票所在的对应文件夹下,便于对照查看和报销提交
  6. 旧版发票:印章覆盖格式的发票,部分字段(购买方、销售方)提取可能不完整,需要人工核对
  7. 批次对应:保持文件路径中的年份和报销批次信息,便于追溯
  8. OFD格式:已支持OFD解析,自动识别三种内部结构(XBRL机票/CustomDatas普通发票/Content.xml文本提取)
  9. 增量缓存:提取结果缓存在 <根目录>/.workbuddy/invoice_cache.json,重复运行只处理新增/修改的文件,大幅提升速度
  10. 按月报销:推荐使用 --month 参数按月整理,输出文件名带月份后缀,避免覆盖
  11. 按年整理:使用 --year 参数,输出文件放在年份文件夹下
  12. 智能引导:不指定 --year 或 --month 时,自动扫描目录结构并提示可用选项
  13. ZIP压缩包:自动解压ZIP文件中的PDF/OFD发票进行提取,来源列标注"ZIP:文件名"
  14. 重复检测:以发票号码为主键自动检测重复发票(行程单用号码+日期组合),重复项标红、首次出现标黄
  15. 报销类别分类(仅按月整理):按销售方名称和项目内容自动分类到 综合费用/差旅费用 的各子类别,移动到 按类别整理/大类/子类别/ 目录(重名时覆盖),只处理原件,重复发票不参与分类
  16. 分类汇总表(仅按月整理):在 按类别整理/ 目录下生成 发票分类汇总表_YYYY-MM.xlsx,包含带报销类别列的发票明细和分类汇总Sheet
  17. 两阶段交互式分类确认(仅按月整理):无法自动识别的发票,使用 --interactive 参数启动分组渐进式两阶段询问流程

两阶段交互式分类确认流程(重要)

⚠️ 脚本本身不会等待用户交互! 需要Agent介入分阶段处理:

  1. 运行 --interactive → 脚本扫描并保存待确认发票到JSON → Agent展示分组并询问用户选择
  2. Agent更新JSON的main_category字段 → 运行 --continue-interactive → Agent询问用户选择子类
  3. Agent更新JSON的sub_category字段 → 运行 --continue-interactive → 脚本处理发票

完整操作流程

第一步:启动交互模式

python invoice_extractor.py "<目标目录>" --month 2026-03 --interactive
  • 脚本扫描发票,发现待确认发票后按销售方分组展示
  • 将待确认发票信息保存到 按类别整理/_pending_invoices.json
  • 重复发票仅在汇总表中标注,不参与分类整理
  • 脚本运行后会输出分组信息和JSON保存路径,然后退出

第二步:Agent询问用户选择大类别

脚本会显示类似以下的分组信息:

  【第1组】湃智造机器人科技(东莞)有限责任公司:共 8 张发票
      - 26319166100004309333.pdf | 项目:未知 | 金额:未知
      - 26449123866000006845.pdf | 项目:未知 | 金额:未知
      ... 共8张

  【第2组】成都携程旅行社有限公司:共 1 张发票
      - 订单1128146963327850-电子普通发票.pdf | 项目:退票费 | 金额:367.92

  请输入: '组序号 大类别' 或 'all 大类别'
  示例: '1 综合费用' 或 'all 差旅费用'

Agent需要使用 AskUserQuestion 询问用户:

请为这些待确认发票选择报销大类别:
- 第1组(8张):选择"综合费用"还是"差旅费用"?
- 第2组(1张):选择"综合费用"还是"差旅费用"?

报销大类别选项:

  • 综合费用(招待费/办公费/行政后勤/采购垫付/物流垫付/其它)
  • 差旅费用(住宿/交通/油补/餐补/通讯/其它)

第三步:Agent更新JSON的main_category字段

用户选择后,Agent必须手动更新 _pending_invoices.json

# 读取JSON
with open('_pending_invoices.json', 'r', encoding='utf-8') as f:
    data = json.load(f)

# 为所有待确认发票设置 main_category(根据用户选择)
for inv in data['pending_invoices']:
    if 'main_category' not in inv or not inv.get('main_category'):
        inv['main_category'] = '差旅费用'  # 用户选择的大类别

# 保存JSON
with open('_pending_invoices.json', 'w', encoding='utf-8') as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

第四步:继续交互,Agent询问用户选择子类

python invoice_extractor.py "<目标目录>" --month 2026-03 --continue-interactive
  • 脚本检测到 main_category 已设置,切换到子类选择阶段
  • 显示按大类分组的子类选项

脚本会显示类似以下的子类选项:

  【综合费用】共 8 张,请选择子类:
    1. 招待费  2. 办公费  3. 行政后勤
    4. 采购垫付  5. 物流垫付  6. 其它

  【差旅费用】共 1 张,请选择子类:
    1. 住宿  2. 交通  3. 油补  4. 餐补  5. 通讯  6. 其它

Agent需要使用 AskUserQuestion 询问用户选择子类。

第五步:Agent更新JSON的sub_category字段

用户选择后,Agent必须手动更新JSON:

# 读取JSON
with open('_pending_invoices.json', 'r', encoding='utf-8') as f:
    data = json.load(f)

# 为所有待确认发票设置 sub_category(根据用户选择)
for inv in data['pending_invoices']:
    if inv.get('main_category') == '综合费用':
        inv['sub_category'] = '招待费'  # 用户选择的子类
    elif inv.get('main_category') == '差旅费用':
        inv['sub_category'] = '交通'  # 用户选择的子类

# 保存JSON
with open('_pending_invoices.json', 'w', encoding='utf-8') as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

第六步:完成处理

python invoice_extractor.py "<目标目录>" --month 2026-03 --continue-interactive
  • 脚本检测到 main_categorysub_category 都已设置
  • 将发票原件移动到对应分类目录:按类别整理/大类/子类/公司主体/发票.pdf
  • 重复发票仅在汇总表中标注,不移动
  • 清理临时JSON文件

JSON结构说明

_pending_invoices.json 的结构:

{
  "pending_invoices": [
    {
      "filename": "xxx.pdf",
      "filepath": "原始文件路径",
      "seller": "销售方名称",
      "buyer": "购买方名称",
      "item": "项目名称",
      "amount": "金额",
      "invoice_no": "发票号码",
      "main_category": "综合费用",  // 第一阶段设置
      "sub_category": "招待费",      // 第二阶段设置
      "is_original": true,
      "duplicate_group": null
    }
  ],
  "groups": [
    {
      "group_idx": 1,
      "seller": "湃智造机器人科技(东莞)有限责任公司",
      "count": 8
    }
  ],
  "phase": "group_sub_category_selection",
  "originals_pending_count": 9
}

报销类别选项汇总

大类别: | 大类 | 子类 | 说明 | |------|------|------| | 综合费用 | 招待费 | 餐饮、酒水、娱乐招待 | | | 办公费 | 办公用品、设备、软件 | | | 行政后勤 | 物业、保洁、维修、水电 | | | 采购垫付 | 材料、五金、建材 | | | 物流垫付 | 快递、运输、仓储 | | | 其它 | 无法归类 | | 差旅费用 | 住宿 | 酒店、宾馆住宿 | | | 交通 | 打车、机票、高铁 | | | 油补 | 加油 | | | 餐补 | 出差餐费 | | | 通讯 | 话费、宽带 | | | 其它 | 无法归类 |

常见问题

Q: 脚本运行后没有停在交互界面? A: 脚本设计为非阻塞式。运行 --interactive 后会扫描并保存到JSON,然后退出。Agent需要:

  1. 读取JSON中的分组信息
  2. 使用 AskUserQuestion 询问用户
  3. 手动更新JSON
  4. 再次运行 --continue-interactive

Q: 如何批量处理? A: 用户可以用 'all 综合费用' 一次性为所有待确认发票选择同一大类别,脚本支持批量格式:'组序号,组序号 大类别'(逗号分隔多个组)。

Q: 重复发票如何处理? A: 重复发票(同一发票号码的多次出现)只在汇总表中标注为"首次出现"或"重复",不参与分类整理和移动。

ZIP内PDF+OFD去重

每个ZIP包内可能同时包含同一发票的PDF和OFD两个版本:

  • 系统优先使用PDF,跳过OFD(字段信息通常更完整)
  • 日志中显示:[去重] ZIP xxx.zip: 跳过 1 个OFD文件(PDF+OFD同号)

依赖

  • Python 3.8+
  • pdfplumber (PDF文本和表格提取)
  • openpyxl (Excel生成)
  • 可选: ofd (OFD格式解析)

更新历史

2026-05-14 修复

问题1:分类汇总表不反映用户确认结果

  • 原因create_categorized_workbook函数使用自动分类结果,没有读取用户已确认的分类
  • 修复:在--continue-interactive模式下,处理完成后重新生成分类汇总表,优先使用用户确认的分类结果
  • 实现:增加confirmed_categories参数,读取_pending_invoices.json中的用户确认结果

问题2:待确认文件夹残留已处理发票

  • 原因:发票被复制到正确分类目录后,原文件没有从待确认目录删除
  • 修复:处理完成后自动清理待确认目录中的已处理发票

问题3:火车票识别失败

  • 原因:火车票的项目名称为空,销售方和购买方相同,无法通过关键词匹配识别
  • 修复
    1. 增加invoice_type_keywords支持:通过发票类型关键词识别
    2. 增加启发式规则:销售方和购买方相同 + 金额在合理范围(10-1000元)+ 销售方包含"科技/机器人"等关键词 → 自动识别为"差旅费用/交通"
    3. 交通类关键词增加:"铁路"、"中国铁路"、"客票"、"动车"、"普速"

历史版本

  • 2026-05-14:初始版本,支持发票提取、ZIP解压、重复检测、两阶段交互式分类确认