《Ecology9—自动化生成开票模板案例》
背景
公司每月需要根据供应商发来的商品结算单,向税局申报开具发票。原来的流程大致是这样的:
- 财务收到结算单 Excel,明细行通常有 100+ 条
- 逐行对照内部商品税目档案,手动填写税局规定的”发票开具项目信息导入模板”
- 上传到电子税务局
整个过程纯手工,单份结算单少则半小时,出错了还要重来。更麻烦的是,结算单里的商品编码和税局要求的税收分类编码是两套体系,需要人工翻译,稍不注意就会填错。
这个需求落到我头上,决定用 OA 系统把这个流程自动化掉。
整体方案
1 | 上传结算单 xlsx |
技术栈:Java 8 + 泛微 e-cology(JAX-RS)+ Apache POI
一、结算单 Excel 解析
结算单的格式是固定的:前 5 行是表头信息,第 6 行是列标题,第 7 行起是商品明细,最后一行是合计行(不要)。
文件接收
泛微平台不支持 javax.servlet.http.Part(Servlet 3.0 的 API),需要用平台自带的 weaver.file.FileUpload:
1 | FileUpload fileUpload = new FileUpload(request, "utf-8"); |
解析主表
表头区域的字段分散在不同单元格,按坐标固定读取:
1 | main.setDdrq(getCellText(sheet, 1, 1)); // R2C2 订单日期 |
公式单元格的坑
解析时遇到一个问题:部分单元格是 VLOOKUP / XLOOKUP 公式,cell.toString() 直接返回公式字符串,比如:
1 | VLOOKUP(C10,[2]国服商品税目维护!$B$1:$H$1150,5,FALSE) |
这显然不是我们想要的。Excel 文件打开过之后,POI 可以读取公式的缓存计算结果,正确的处理方式是:
1 | CellType type = cell.getCellType(); |
防重
以原合同编号作为唯一键,导入前先查重:
1 | checkRs.executeQuery("select id from uf_spjsd where yhtbh = ?", main.getYhtbh()); |
二、税局模板生成
税局的发票模板格式固定(三行表头 + 数据行),数据列映射关系如下:
| 税局模板列 | 数据来源 |
|---|---|
| 项目名称 | 商品税目表.项目(xm) |
| 税收分类编码 | 商品税目表.税收分类编码 |
| 规格型号 | 结算单明细.规格型号 |
| 单位 | 固定”个” |
| 商品数量 | 结算单明细.采购数量 |
| 商品单价 | 结算单明细.经销价格 |
| 金额 | 结算单明细.结算金额 |
| 税率 | 固定 0.13 |
关联逻辑:结算单明细表通过商品编码(spbm)LEFT JOIN 商品税目维护表,商品税目维护表是财务平时自己维护的,存放了每类商品对应的税局项目名称和税收分类编码。
读取模板 + 填充数据
模板文件存在 OA 附件系统,通过 docId 读取,保留原模板的前 3 行(填表说明 + 字段限制 + 列标题),从第 4 行(index=3)开始写数据:
1 | try (InputStream in = new FileInputStream(tempFile); |
生成后上传到 OA 附件
泛微上传文件需要把字节数组包装成 multipart 请求:
1 | String fileName = String.format("发票开具项目信息导入模板-%s-%s.xlsx", xssyf, yhtbh); |
三、接口设计
| 接口 | 说明 |
|---|---|
POST /api/invoice/import/importSpjsd |
上传解析结算单 xlsx |
POST /api/invoice/spjsd/list |
分页查询历史结算单 |
GET /api/invoice/spjsd/detail?statementId=xxx |
查询指定结算单明细 |
POST /api/invoice/spjsd/generate |
生成税局模板,返回 fileid |
/generate 接口接受 statementId(结算单 ID)和 docId(模板文件的 OA 附件 ID)两个参数,模板文件和生成逻辑解耦,税局更新模板格式时只需替换模板文件,不需要改代码。
四、一些细节
商品税目未维护的处理
LEFT JOIN 而不是 INNER JOIN,这样即使某个商品编码在税目表里没有记录,对应的明细行也会出现在生成的模板里,只是项目名称和税收编码为空。财务看到空行可以手动补填,而不是整个文件生成失败。
税率和单位的扩展性
目前税率固定 0.13,单位固定”个”,已记录为技术债。后续可以在商品税目维护表里增加税率和单位字段,生成时按商品读取,而不是全局固定值。
模板版本问题
税局偶尔会更新发票模板格式。把模板文件存在 OA 附件里、通过 docId 传入的设计,可以让财务自己更换模板文件,而不需要找开发


