自动化差旅费用报销流程,支持12306火车票、携程机票/酒店、华住会酒店、美团、吉祥航空、百望等电子发票的自动获取、解析和报销提交。支持邮件附件下载和链接下载两种发票获取方式。触发条件:用户提及差旅报销、火车票报销、12306发票、携程发票、华住会发票、酒店报销、机票报销、电子客票、美团发票、航空发票、百望发票等。
📊 完整流程图见
references/flowchart.md,建议先阅读流程图了解整体流程。
支持:
功能:读取邮箱、下载PDF附件/通过链接下载发票PDF、解析发票信息、生成报销数据、上传发票到FOL、提交差旅报销
发票获取方式:
限制:非发票类邮件会被过滤掉,但不会人为限制发票类型
技能/模块用途url_downloader.py给定 URL,curl 优先下载 → Playwright 兜底(邮件正文中发票链接的下载)pdf-ocrPDF/图片OCR文字提取、发票信息解析browser浏览器自动化(仅用于 FOL 报销系统,发票上传和报销单提交)fol-invoice-uploadFOL发票上传,通过浏览器自动化将发票PDF上传到财务系统并完成OCR识别fol-travel-expenseFOL报销申请,通过浏览器自动化创建国内差旅报销单并提交himalaya邮件搜索、下载附件(直接下载邮件中的 PDF/OFD 附件)
所有涉及 IMAP 连接的操作必须严格串行执行,禁止并发调用同一邮箱账号。
imaplib.IMAP4_SSL() 操作完成后记得 mail.logout()OCR 解析必须一张一张串行执行,禁止并发/并行调用多个 OCR 进程。
RapidOCR 每进程需加载模型占用 350MB+ 内存,并行会导致内存暴涨触发 OOM Killer 杀死 gateway 进程。
工作目录: /tmp/openclaw/uploads/{session_id}/(每次报销使用独立的动态子目录,如 /tmp/openclaw/uploads/run-20260529-001/)。
创建子目录:
text
/tmp/openclaw/uploads/{session_id}/
├── inbox/ # 所有发票PDF统一存放,不做分类子目录
├── processed/ # 已解析的发票数据
└── output/ # 生成的报销文件
动态目录命名规则:
run-{YYYYMMDD}-{序号},先检查/tmp/openclaw/uploads/下已有目录避免冲突。
四步判断流程(严格按顺序执行):
通过 himalaya envelope list 获取今日邮件列表,根据发件人和主题过滤出发票邮件。
对每封邮件判断: himalaya message read <msg_id> 查看邮件结构,确认是否有 PDF/OFD 附件( has_attachment: true 的邮件)。
对有附件的邮件,执行:
bash
himalaya attachment download <message_id> -d /tmp/openclaw/uploads/{session_id}/inbox/
附件直接落入 inbox/,标记该邮件处理完毕,跳过第三、四步。
对无附件的邮件,读取正文( himalaya message read <msg_id>),按以下优先级提取下载 URL:
re.findall(r'https?://[^\s"'\'<>]+', text) 从 HTML 正文直接匹配 http 链接email 模块解析原始邮件,对每个 part 的 text/html 或 text/plain 内容深度扫描 URL常见发件人 URL 特征:
pis.baiwang.com/smkp-vue/previewInvoiceAllEle?param=einvoice@juneyaoair.com / www.juneyaoair.com/eflight/invoice调用 url_downloader.py 下载:
python
import sys
sys.path.insert(0, '/app/skills/travel-expense-reimbursement/references')
from url_downloader import download_file
filepath = download_file(
url='<提取到的URL>',
save_dir='/tmp/openclaw/uploads/{session_id}/inbox/',
filename_hint='invoice.pdf',
)
下载策略(curl 优先,Playwright 兜底):
text
curl 直接下载
↓ 失败(超时/非PDF响应)
Playwright 访问页面
→ 页面直接触发 PDF 下载?拦截保存
→ 未触发?查找并点击下载按钮
→ 找到 PDF 直链?curl 下载
→ 全部失败 → 截图备用,告知用户手动下载
扩展新的发票下载类型(在 url_downloader.py 中添加 handler):
python
def download_myinvoice(page_url: str, save_dir: str) -> Optional[str]:
return download_file(page_url, save_dir)
URL_PATTERN_HANDLERS.append((
'https://myinvoice.example.com/',
download_myinvoice,
))
⚠️ 吉祥航空特别说明:部分吉祥航空邮件正文中的"下载PDF文件"仅为文字按钮,非真实超链接。若第二步和第三步均无法获取真实下载地址,应告知用户手动从吉祥航空App或官网下载发票附件后发送至邮箱。
批量解析:所有 PDF 一次性完成,不逐张调用。
python
import sys
import os
import glob
sys.path.insert(0, '/app/skills/pdf-ocr-skill/scripts')
from pdf_ocr_processor import PDFOCRProcessor
inbox_dir = '/tmp/openclaw/uploads/{session_id}/inbox/'
pdf_files = sorted(glob.glob(os.path.join(inbox_dir, '*.pdf')))
processor = PDFOCRProcessor(engine='rapid')
results = []
for pdf_path in pdf_files:
result = processor.ocr_pdf(pdf_path)
results.append({
'pdf_path': pdf_path,
'filename': os.path.basename(pdf_path),
'text': result['text'],
'page_count': result['page_count']
})
print(f"[OK] {os.path.basename(pdf_path)} ({result['page_count']}页)")
# 输出汇总供后续步骤使用
import json
print('\n=== OCR_RESULTS_JSON ===')
print(json.dumps(results, ensure_ascii=False))
results 列表中,进入步骤5汇总/tmp/openclaw/uploads/{session_id}/processed/invoice-list.json依据 references/行程分析指南.md 分析行程,先自行分析整理,遇到不确定的再问用户。
以下参数从发票自动推断,不需要问用户:
只需要确认两个参数:报销事由和项目名称。
通过 memory_search 搜索历史报销记忆,关键词: 报销 项目名称、 差旅报销 事由
向用户一次性确认:
text
请确认:
- 报销事由:出差(记忆中最常用)/ 或用户输入
- 项目名称:????(历史曾用:A、B、C)/ 或用户输入新项目
其余参数不询问,从发票自动填充。
用户确认后,只将 事由和项目名称 写入 memory/YYYY-MM-DD.md:
markdown
## 差旅报销 (当日日期)
- 事由: XXX
- 项目名称: XXX
调用 fol-invoice-upload 技能, 一次性批量上传所有发票PDF(不要逐张上传)。
收集所有待上传发票的文件路径列表,一次性传递给上传流程(如果上传流程支持批量),或尽可能连续快速上传减少等待。
发票文件路径使用 /tmp/openclaw/uploads/{session_id}/inbox/ 下的文件。
调用 fol-travel-expense 技能,按行程分段提交报销单。
向用户展示:处理发票数、总金额、行程汇总、提交状态。
错误处理第一步 判断失误/邮件获取失败降级到 Python imaplib 直连获取第二步 himalaya 下载附件失败降级到 Python imaplib 直连第三步 无法提取下载地址(按钮文字非链接)告知用户手动从发件方官网/App下载,询问是否上传PDF第三步 正则提取 URL 失败尝试 Python email 模块深度解析 MIME 结构第四步 url_downloader curl 失败自动切换 Playwright 浏览器方案第四步 Playwright 仍失败(超时/需登录/验证码)截图备用,告知用户该链接无法自动下载,询问是否手动上传PDFPDF解析失败保留文件,继续处理其他的发票类型无法识别记录到待处理,提示用户fol-invoice-upload 失败记录失败发票,提示手动上传fol-travel-expense 失败保存数据,提示手动提交
发票类型搜索关键词(from)主题特征发票获取方式12306火车票12306"电子发票通知"邮件附件携程机票/酒店trip"机票"/"行程单"/"航班"/"酒店"/"住宿"邮件附件去哪网机票/酒店qunar"机票"/"行程单"/"航班"/"酒店"/"住宿"邮件附件华住会酒店huazhuhotel"发票"/"电子发票"邮件附件美团发票meituan"发票"/"电子发票"邮件附件吉祥航空机票juneyaoair航空/行程单/发票url_downloader.py(curl 直链,失败切 Playwright)百望发票baiwang发票/电子发票url_downloader.py(GreenPaper 跨域 iframe,截图备用;建议手动下载 PDF 上传)