基于Airtable数据库的ZUGFeRD电子发票:使用PDF4me在Make中构建 JSON 并通过电子邮件发送混合 PDF

PDF4me Create a ZUGFeRD invoice是一个Make模块,它将结构化发票数据转换为混合PDF/A-3电子发票。该模块指向一个Airtable数据库,读取一行数据,将其转换为EN 16931 XML,返回一个既可读又可机器解析的文件,并将其放回记录中。
搜索电子发票软件,你会看到供应商对比:平台、套件、单页定价。搜索Airtable是否可以生成发票,你会看到一大堆帖子解释说它不能。介于这两种结果之间的是这样一个团队:他们已经在使用Airtable,却被告知发票必须机器可读,并且他们不想为了满足格式要求而将业务迁移到其他发票平台。本文正是为他们量身打造的。
一行数据被添加到Airtable表格中,包含卖家、买家、一个商品项和总计。Make Make该数据,根据列构建一个简洁的 JSON 对象, PDF4me将其转换为嵌入EN 16931 XML 的PDF/A-3文件。该文件作为附件返回到记录中,并通过电子邮件发送给买家。五个模块,无需编写 XML 代码。
基于原因的问答
Why run this from Airtable rather than an invoicing product?因为发票数据通常是已在其他地方跟踪的工作的副产品。如果项目、客户和约定的价格都存储在数据库中,那么该数据库就是最接近真实数据源的东西。为了满足文件格式的要求而将其迁移到产品中,意味着同一个客户的信息将永远存在于两个地方。
Why describe the invoice as JSON instead of writing the XML?因为跨行业发票语法冗长、嵌套层级深,而且对代码位置的要求非常严格。JSON 对象用更少的空间就能表达相同的信息,其键也更符合业务术语, PDF4me会根据 JSON 对象生成符合规范的 XML。最容易出错的部分往往是你没有维护的部分。
Why does the hybrid file matter?因为发票有两个受众。买方的会计系统读取嵌入的 XML 数据,无需人工录入即可完成记账;应付账款人员打开同一个文件,看到的是一页内容。一份文件就能满足两者的需求,这就是原因。FeRD该格式背后的德国论坛就是这样构建的,而不是仅仅指定 XML。
Why attach the result back to the record?因为找不到电子发票只是把问题延后了而已。将 PDF 文件放入该行的Document字段中,就能将该文件与生成该文件的数据关联起来,这正是您在买家对 18 个月后的金额提出异议时所需要的。
您将获得
输入:一条Airtable记录,包含发票编号、日期、卖家信息栏、买家信息栏、一个项目明细和总计, Status设置为“新建”。输出:一个ZUGFeRD PDF/A-3附件,并同时向买家发送一封包含该文件的电子邮件。

每张发票对应一行。这里的每一列都会成为场景构建的 JSON 中的一个键。

买家看到的是一张普通的发票。他们的软件看到的是发票中的 XML 数据。
你需要什么
- A Make account。Open Make. 五个关于标准应用程序的模块。
- PDF4me API key。Get your API key一个PDF4me连接即可满足此场景的需求。
- An Airtable base捕获的基础是
PDF4me Live Automations,其中包含一个名为ZUGFeRD Requests的表,该表包含发票抬头、包含增值税 ID 的卖家块、买家块、行项目字段、总计、电子邮件列、 Status字段和名为Document”的附件字段。 - A Gmail account用于交付,与Make连接。
- Your seller details including a VAT ID它们来自列,因此一个数据库可以为多个法人实体开具发票。
概览
- Watch records (Airtable) 从
ZUGFeRD Requests中获取新行。 - Create a ZUGFeRD invoice ( PDF4me)将 JSON 转换为混合 PDF。
- Upload an attachment ( Airtable)会将其附加到同一行的
Document字段中。 - Update a record ( Airtable)会将结果写回。
- Send an email ( Gmail)将电子发票发送给买方。
完整场景概述

与瑞士QR方案相同的五模块结构,仅中间的PDF4me模块有所不同。
步骤 1:如何从Airtable行触发场景?
目前流程:尚未启动,这是触发点。
标记为“就绪”的行表示请求开具发票。
- 添加Airtable > Watch records作为触发器。
- 配置:
- 根据:
PDF4me Live Automations - 桌子:
ZUGFeRD Requests - Trigger field:
Created - Label field:
Title - 限制:
10 - 公式:
{Status}='New'
- 根据:
Watch records配置

Status是关键所在。只有当有人将行设置为“新建”时,才会发布任何内容。
步骤 2:如何将一行转换为ZUGFeRD发票?
目前为止的流程: Airtable触发器。
这是生成电子发票的模块。设置项决定发票格式;JSON 数据决定发票内容。
- 添加PDF4me > Create a ZUGFeRD invoice。
- 配置输出:
- File Name:
ZUGFeRD_+ InvoiceNumber +.pdf - Input Format:
JSON - Output Mode:
XML With PDF - ZUGFeRD Version:
2* - Conformance Level:
EN16931 - Render Invoice on PDF:
Yes
- File Name:
- 将发票对象粘贴到Invoice JSON Data中,并映射其中的列:
{
"InvoiceId": "{{1.InvoiceNumber}}",
"TypeCode": "380",
"InvoiceType": "RECHNUNG",
"IssueDate": "{{formatDate(1.InvoiceDate; "YYYY-MM-DD")}}T00:00:00",
"DeliveryDate": "{{formatDate(1.InvoiceDate; "YYYY-MM-DD")}}T00:00:00",
"IsTest": false,
"Seller": {
"Name": "{{1.SellerName}}",
"VatId": "{{1.SellerVATID}}",
"Address": {
"Street": "{{1.SellerStreet}}",
"PostalCode": "{{1.SellerPostalCode}}",
"City": "{{1.SellerCity}}",
"Country": "{{1.SellerCountry}}"
}
},
"Buyer": {
"Name": "{{1.BuyerName}}",
"Address": {
"Street": "{{1.BuyerStreet}}",
"PostalCode": "{{1.BuyerPostalCode}}",
"City": "{{1.BuyerCity}}",
"Country": "{{1.BuyerCountry}}"
}
},
"LineItems": [
{
"ProductName": "{{1.ItemDescription}}",
"Quantity": {{1.Quantity}},
"UnitCode": "C62",
"UnitPrice": {{1.UnitPrice}},
"LineTotal": {{parseNumber(1.NetTotal; ".")}}
}
],
"Totals": {
"Currency": "{{1.Currency}}",
"LineTotal": {{parseNumber(1.NetTotal; ".")}},
"TaxBasisTotal": {{parseNumber(1.NetTotal; ".")}},
"TaxPercent": {{1.VATRate}},
"TaxTotal": {{parseNumber(1.VATAmount; ".")}},
"GrandTotal": {{parseNumber(1.GrossTotal; ".")}},
"ChargeTotal": 0,
"AllowanceTotal": 0
},
"Payment": {
"Currency": "{{1.Currency}}",
"PaymentReference": "{{1.InvoiceNumber}}",
"DueDate": "{{formatDate(1.DueDate; "YYYY-MM-DD")}}T00:00:00"
}
}
该TypeCode有三个部分值得注意。TypeCode 380是商业发票的代码,它是固定的,并非源自任何基础UnitCode。UnitCode C62是联合国/欧洲经济委员会 (UN/ECE) 编码,表示“某物的一个”,也就是您用来表示服务时长或单位的代码。末尾的Payment块包含PaymentReference,这是买方付款时提供的文本,在这里就是发票号码。
Create a ZUGFeRD invoice配置

该字段在编辑器中可以滚动,因此一次只能看到对象的一部分。上面的代码块就是它的全部内容。
每个设置决定什么
| 场地 | 这里的价值 | 它控制着什么 |
|---|---|---|
| Input Format | JSON | 请提供结构化对象,而不是现成的 XML 或 CSV 行。 |
| Output Mode | XML With PDF | 返回一个混合文件。XML XML Only返回发票 XML 文件。 |
| ZUGFeRD Version | 2* | 2.x 代产品符合EN 16931标准,而旧版 1.0 产品则不符合。 |
| Conformance Level | EN16931 | 发票声称符合的规范,以及因此 JSON 必须提供的键。 |
| Render Invoice on PDF | Yes | 根据相同的数据生成易于阅读的发票页面。由于没有提供基础 PDF 文件,这赋予了文件人性化的一面。 |
| Document | 空的 | 这里原本应该放置带有您信头的基础 PDF 文件。如果留空, PDF4me会渲染一个空白页面。 |
Tip.金额和数量密钥必须以纯数字形式传入,而不是带引号的字符串,这就是为什么它们要经过以下流程:parseNumber文本字段用引号括起来。"1005"如果是普通的,则会被拒绝。1005接受此操作,并且返回的错误信息将与模式相关,而不是与引号相关。请注意第二个参数:parseNumber(1.NetTotal; ".")告诉系统Make小数分隔符设为点号。省略点号,并设置一个会写入的区域设置。1005,00解析结果会出错而不是失败,这是最糟糕的结果。
步骤 3:如何将电子发票重新添加到记录中?
目前流程:触发器加上PDF4me模块。
- 添加Airtable > Upload an attachment。
- 配置:
- Base和Table:同步骤 1
- Record ID:触发器中的
id - Attachment field:
Document - File: PDF4me的输出 - Create a ZUGFeRD invoice
- File name:
ZUGFeRD_+ InvoiceNumber +.pdf - Data:模块 2 的Document输出。
- Content type:
PDF file
Upload an attachment配置

此处的Content type为PDF file。瑞士 QR 码的对应版本则在同一位置使用 PNG 图片。
步骤 4:如何将结果记录在行中?
目前流程:所有操作均通过附件上传完成。
- 添加Airtable > Update a record。
- 配置:
- Base和Table:同步骤 1
- Record ID:触发器中的
id - Record:仅填写您希望写入的内容,通常为Status和OutputLink
Update a record配置

表格中的每一列都会显示在这里。截图显示这些列为空,面板也处于滚动状态,所以请特意设置Status。
Tip.如果某个字段已映射但为空,则会清除该单元格。在发票表中,这意味着增值税号为空,或者在运行结果显示成功的情况下丢失了到期日,因此请不要修改任何您并非有意写入的内容。
第五步:如何向买家发送电子发票?
目前流程:所有操作均已完成记录更新。
数据库中尚未开具电子发票。
- 添加Gmail > Send an email。
- 配置:
- To:触发器中的Email列
- Subject:
Your e-invoice及InvoiceNumber - Body type:
Raw HTML - Content:一段简短的HTML正文,包含买家姓名、发票号码和总金额。
- 添加附件:
- File: PDF4me - Create a ZUGFeRD invoice输出
- File name:
ZUGFeRD_+ InvoiceNumber +.pdf - Data:模块 2 的Document输出。
Send an email配置

附件来自模块 2,因此Airtable永远不会读取该文件。
运行场景并验证
- Save方案并运行一次。
- 向
ZUGFeRD Requests中添加一行,其中包含卖家、买家、一个项目、两个日期和StatusNew。 - 五个模块都应该各自报告一项操作。
- 打开该行:
Document字段应包含一个以发票编号命名的 PDF 文件。 - 打开附件。您应该可以看到一个可读的发票页面,并且ZUGFeRD验证器应该在同一个文件中找到EN 16931 XML 代码。
- 查看买家收件箱,找到邮件及其附件。
在捕获的运行中, RE-2026-0009行生成了一张来自Berlin Muster GmbH (增值税号DE123456789的发票,开给Muenchen Test Kunde AG,内容为 10 个单位的Consulting,每个单位 100.50,电子邮件报告的总金额为 1195.95 欧元。
Do not trust the grid for decimals.Airtable表格显示VATAmount 191, GrossTotal 1196,而发票和电子邮件中显示的分别为190.95和1195.95。表格并未重新计算: TaxTotal和GrandTotal直接取自这两列。表格只是按照数字字段设置的精度显示这些数值。在尝试将某一列与发票进行核对之前,请提高该精度,因为这些数字看起来不一致,但实际上并非如此。
无需重建即可添加的常见变体
与其他电子发票生成方式相比,这种方式有何优势?
每一行描述了一种替代方案。最后一行就是当前的情况。
| 比较 | 谁在运营它 | 数据来源 | 你所维护的 |
|---|---|---|---|
| 电子发票平台与此场景的比较 | 通过门户网站进行金融交易 | 上传或集成 | 按文件签订合同及其关联 |
| ERP模块与此场景 | ERP系统中的财务部分 | ERP自身的账簿 | 许可证,以及将您的数据迁移到新系统 |
| 开发者库与此场景 | 应用程序内部的开发人员 | 通过代码实现您的数据库 | 代码、架构更新和版本发布 |
| 这种情况 | 任何可以添加Airtable行的人 | 团队已经保留的基础 | 五个模块和一个 JSON 对象 |
常见问题
什么是应付账款自动化?
在接收端,这意味着发票到达后,无需人工输入即可读取其字段,核对数字,然后将结果发布或提交审批。混合型电子发票确保了读取步骤的可靠性,因为数据是嵌入在发票中的,而不是从页面图片中提取的。
这种情况属于该安排的发送环节。生成买方系统可以解析的文件,才能让他们的应付账款自动运行。
Airtable可以创建发票吗?
不符合规范的。Airtable 可以存储Airtable数据,并将其布局在界面或可打印视图中,但它无法生成符合EN 16931 XML 标准的PDF/A-3文件,而这正是电子发票法规的要求。
此场景采用的拆分方式是实际可行的: Airtable是记录, PDF4me生成工件,工件作为附件返回到记录中。
应付账款自动化是如何运作的?
它分三个部分运行:接收、提取和过账。接收是将文档集中到一个地方;提取是将文档转换为字段;过账是将这些字段写入账本,通常前面会设置一个置信度或审批机制。
混合式电子发票简化了中间环节。当 XML 数据已存在于文件中时,提取过程就变成了解析而非识别,这也是为什么相关法规不断推动这一方向发展的原因。
什么是Airtable自动化?
Airtable自带自动化构建器,用于在数据库内部创建触发器和操作,这足以完成大部分内部工作。但它无法调用文档 API 并返回附件字段所需的文件,而这正是本场景所需要的步骤。
正是出于这个原因, Make才放在旁边。触发器仍然监视着一个Airtable表格,但场景中间部分可以访问Airtable自身自动化功能无法访问的服务。
故障排除
在运行历史记录中打开模块的输入包,并将其读取为 JSON 格式。如果Airtable列为空,则对应的键也为空;此外,如果货币值保留了引号,则该值会以文本形式返回,而预期是数字。这两种情况都会生成看起来没问题的对象,但转换仍然失败。
Conformance Level决定了配置文件所需的字段。如果 JSON 文件缺少EN16931配置文件所需的键,即使文件可以正常打开,验证也会失败。请补充缺失的键,或者降低配置文件的级别。
Render Invoice on PDF已设置为“否”,且未提供基础 PDF 文件,因此嵌入的 XML 文件中没有任何内容。该文件仍然是有效的电子发票,但其内容部分为空。
检查Output Mode是否为XML With PDF,然后检查模块 3 和 5 是否都从模块 2 获取数据。附加其他任何内容都会发送一个看起来正确的页面,但其中包含他们的软件无法读取的任何内容。
后续步骤
授权请求通常以文件格式问题的形式出现。这实际上取决于您的发票数据存储在哪里,以及是否有任何程序可以读取这些数据。