← 返回文章列表

把报销单交给脚本:手机上一键出验收单和入库表

发票链接进,zip 出:验收单、入库表、照片、时间轴一次备齐。附 .docm 不装 Office 直改 OOXML 的做法,和六个真踩过的坑。

报销这件事烦的从来不是填表,是每次都从头填一遍:发票翻出来、金额加一遍、名字抄两遍,抄错一个数就得整份重打。

浅木桌上揉皱的一叠票据,旁边一支钢笔和一枚回形针
真正让人烦的从来不是那几行字,是这一堆东西每次都要重新理一遍

这份活儿我在手机上把它接走了:给个电商平台的订单链接,两分钟后拿到一个 zip,里面有验收单、入库记录表、发票 PDF、照片,还有一个自动累加的采购时间轴。下面把这套东西怎么长出来的讲清楚,包括它到底替人省了哪几步、哪些地方还得人自己拿主意。

一、它到底替谁干活

单位的报销模板是两份带宏的 Word 文档:一张验收单,一张入库记录表。栏位是单位定好的,长什么样不重要——重要的是这些栏位里要填的东西,九成都能从发票 PDF 里直接读出来。

手填的问题不在于难,而在于每批都要把同一套信息抄两遍、再把发票金额加一遍。一学期几十张发票,抄错一个数字就得整份重打。

所以整套流程被拆成六个动作:

真正需要人做的只剩三件事:告诉它这批是干什么用的(用途)、采购日期、以及拍一张物品照片。其余全是脚本的活。

二、发票从哪来:链接进,PDF 出

电商平台的订单页能直接导出电子发票,但要点五六下、还要选抬头。这步最没技术含量也最容易烦人,所以直接让脚本去点:把订单的分享短链(一条短链接)写进一个文本文件,一票一行,脚本自己下载 PDF 落盘。

下载完立刻做一次体检,屏幕上直接打出来:

发票:某某品牌猫粮 -> 已下载
物品总价 = 29.88
✅ 抬头:含指定单位名称(销售方 某某贸易公司)

这一步的价值不在下载,而在把"抬头错了"这类返工挡在填表之前。发票的买家名称里必须含指定的单位名称,税号 1 开头是机关事业单位(买方),9 开头是企业(卖方)——这两个判断写死在脚本里,错的票当场报出来,不往下走。

解析 PDF 用的是 pypdf。这里踩过一个坑:原本想用 PyMuPDF(fitz),在 Termux 上装不上,改用 pypdf 的 extract_text() 之后发现对电子发票反而更稳——电子发票是文本层,不是扫描件,一把提干净。

三、核心那一步:不装 Office 也能填 .docm

这是整套东西里唯一有点门槛的地方,也是我最想讲的部分。

验收单和入库表的模板是 .docm——带宏的 Word 文档。常规解法是 python-docx 或 Windows 上开 Word 用 COM 自动化,两条路在手机上都是死路:

.docm 的本质是个 zip。解压之后是 word/document.xml(正文)、word/vbaProject.bin(宏)、word/styles.xml(样式)、word/media/(图片)。宏、页眉页脚、字体设置全在这些部件里。

于是思路变成:不解构、不重建,只改一个格子里那段 XML。具体做法是定位到目标单元格(行列都从 1 开始,跟 Word 的 Cell(r,c) 一致),在它的段落里插一个 run 写文字,模板原有的段落属性和字体设置原封不动;图片则换算成 EMU(1 磅 = 12700 EMU)等比缩放后写进 drawing 标签。改完重新打回 zip,宏和样式一个字节都没动。

这套引擎单独成一个模块,348 行,只用标准库(zipfile + re + struct),Windows、Linux、安卓 Termux 跑的是同一份代码。

有个坑值得单独说:批量脚本里原本无条件 word = Word()(COM 对象),在有 Office 的 Windows 上没事,在手机上直接抛 ModuleNotFoundError: win32com——报错位置离真正原因很远,查了半天才发现是"引擎选择"那段判断写漏了条件。现在改成先判引擎再决定要不要碰 COM,纯 Python 引擎才真正生效。

四、照片:先裁隐私,再进格子

实物照片好办,等比缩放进单元格就行。麻烦的是很多人只给订单截图。

替用户做这一步前先说清楚:截图不是实物照片,财务认不认由人定,不替他拍板。真要用,只留"店铺 + 商品 + 实付款"那一段,快递员姓名、代收点地址、订单编号一律裁掉——这些信息本来就不该出现在交给财务的表格里。

裁完再用视觉模型复查一遍有没有残留的隐私文字,确认干净才塞进表格。原始截图不动,裁切版另存。

五、最后一步:打包和一张会累加的时间轴

每批跑完,成品打成一个 zip:两份填好的 docm、发票 PDF、发票汇总、照片、批次信息,七个文件,路径带批次名前缀,别人解开不会散一地。

顺手还会重算一张采购时间轴:

累计金额不是装饰。它是自检:这一批入账之后,累计数应该等于"上一轮累计 + 本批金额",对不上说明有一批没进去或者进了两次。比起翻文件夹数发票,这个数字一眼就能验。

牛皮纸文件袋里露出一叠票据,袋口别着一枚黄铜夹
跑完之后,它该是这样一个能直接交出去的东西

六、踩过的坑,挑六个

  1. fitz 在 Termux 装不上,换 pypdf;电子发票有文本层,够用。
  2. python-docx 会丢宏,.docm 必须走 OOXML 直改。
  3. 发票商品名不能直接抄。明细里是一整串关键词堆砌("某某品牌猫粮 鲜肉美毛 幼猫成猫 美短英短蓝猫暹罗猫 鱼肉味 2kg"),塞不进"品牌型号"和"规格"两列,得人工提炼一次并记进校正文件。
  4. 用途别用默认值。脚本默认按"办公耗材"往下填,遇到猫粮、文具这类就填错,所以用途必须人来给。
  5. 备份不能打进自己目录。第一次把备份 zip 放在库根目录,第二次备份连自己一起打包,包越滚越大。
  6. 重复下载要能跳过。网络抖动重跑一次,已经下过的 PDF 直接复用,不然同一张发票会出现两份、金额加两遍。

七、它不干什么

  • 抬头不对不将就。买家名称不含指定单位,脚本直接停,让人回电商平台重开,不替你凑合。
  • 不给商品名编造型号。发票写什么、订单页写什么,两者冲突时报给人决定。
  • 不替人判断财务认不认截图。照片能不能当凭证是财务口径,不是脚本口径。
  • 不做假。金额、日期、发票号全部从 PDF 文本层读,读不到就报错,不猜、不补。

真正省下来的其实是注意力:原来要在一堆发票和两个表格之间来回搬数字,现在只需要三句话——用途是什么、哪天买的、照片在哪。剩下的交给那条命令。

▍ 本文由自建 Agent 调度内容流水线自动完成:写稿 → 模型 API 生图 → R2 图床 → 排版 → MCP 落站。 ▍ 配图由模型 API 生成、托管在对象存储上;涉及的机构与商品名称已脱敏,内容仅供参考。