西班牙合规

Odoo 西班牙 Facturae 为什么不只是“导出 XML”:签名证书、行政中心与红字更正发票链路讲透

很多人把 Facturae 理解成西班牙版电子发票导出,但 l10n_es_edi_facturae 源码显示,真正难点在于企业证书、行政中心角色、付款分期明细、红字更正发票的原因码与被冲原票映射,以及导入时如何识别和拆分 XML。本文把这条合规链路讲透。

框架
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

很多人会把 Facturae 想成“西班牙电子发票导出 XML”,但从 /home/ubuntu/odoo-temp/addons/l10n_es_edi_facturae/models/account_move.py 来看,真正复杂的不是导出动作本身,而是一张西班牙发票在变成可交换、可签名、可纠错的合规对象之前,需要先把业务上下文补齐到什么程度

这条链至少包含:

  1. 公司有没有可用于 Facturae 的证书。
  2. 伙伴、国家、VAT、行政中心角色是否齐。
  3. 支付条款能否转换成 XML 里的 installment 结构。
  4. 红字 / 更正发票是否能追溯到被冲原票及原因码。
  5. 导出的 XML 能否再被系统识别、拆分和导回。

所以最短结论是:

Facturae 在 Odoo 里不是“文件格式支持”,而是一套把会计对象提升为西班牙电子发票合规对象的转换工程。


为什么默认启用条件卡得这么严

_l10n_es_edi_facturae_get_default_enable() 会同时检查:

  • 没有现成 PDF report;
  • 没有已有 Facturae XML;
  • 不是 simplified invoice;
  • 是 invoice / receipt;
  • 国家是 ES;
  • 公司存在 Facturae 证书。

这说明 Odoo 完全没把 Facturae 当成“任何票都可以顺手导一下”的附加按钮。

它更像是在问:

  • 这张票是不是本来就适合进入这一合规链;
  • 公司当前是否具备签名与导出条件。

这类默认启用逻辑很重要,因为合规模块最怕“按钮先亮着,真正导的时候才一路报错”。


为什么证书是第一前提,而不是导出后的补救

_l10n_es_edi_facturae_render_facturae() 在渲染模板后,会调用 _l10n_es_facturae_sign_xml()。如果没有有效证书,虽然 XML 可能渲染出来,但会把“未签名”作为错误返回。

而在公司侧检查里,也明确要求:

  • 公司必须配置有效 Facturae 证书。

这说明官方把签名看成文件可靠性的一部分,而不是一个“有空再补”的后处理动作。

原因很好理解。

对于很多政府或公共部门接收场景来说,电子发票的价值不只是结构化 XML,还包括签名证明它的出具主体和内容完整性

所以在这类链路里:

  • XML 是容器;
  • 签名是信任层。

少了后者,前者往往只剩“长得像”。


行政中心为什么说明它不是普通 B2B 发票导出

_l10n_es_edi_facturae_get_administrative_centers() 会遍历 partner 下 type == 'facturae_ac' 的子联系人,并把以下信息带进模板:

  • center code
  • role type
  • physical GLN
  • logical operational point
  • phone / country code 等

这很有代表性。

因为它说明 Facturae 不是简单的“卖方 / 买方 / 金额 / 税额”四件套,而是要能描述公共机构或大型组织内部的行政路由结构

换句话说,系统不是把发票发给一个抽象公司名,而是要足够清楚:

  • 发给哪个行政中心;
  • 这个中心扮演什么角色;
  • 该走哪个操作点。

这也是为什么模块里专门给 partner 扩了行政中心相关字段和校验。


红字更正发票为什么必须追溯原票

_l10n_es_edi_facturae_get_corrective_data() 对 refund 的要求非常严:

  • 如果没有 reversed_entry_id,直接报错;
  • 强调这张 credit note / refund 必须直接从关联原票创建;
  • 然后再通过 _l10n_es_edi_facturae_get_refunded_invoices() 找回被冲原票;
  • 带上 tax period、reason code、reason description。

这说明 Facturae 的更正逻辑不是“负数票也能导出”那么简单,而是:

你必须解释自己改的是哪一张票、在什么税期、因为什么原因改。

这正是合规模块和普通导出最大的差异:

  • 普通导出关注结果长得像;
  • 合规导出关注结果是否可追责、可回链、可审计。

为什么付款条款会被展开成 installments

_l10n_es_edi_facturae_convert_payment_terms_to_installments() 会把 payment term line 转成 XML 里的 <Installment> 节点,包括:

  • 到期日;
  • 分期金额;
  • payment means;
  • 收款账户的 IBAN / BIC。

这说明在 Facturae 语境里,付款条件不是一个给人看的备注,而是结构化交易条款。

对系统来说,这很关键,因为一张发票的支付安排会影响:

  • 对方如何理解收款方式;
  • 公共部门系统如何自动化处理;
  • 后续对账与审计是否有明确依据。

也就是说,在这里 payment terms 已经从 UI 文本,变成了交换协议字段


发票行为什么要重建一套 XML 语义,而不是直接复用 Odoo 行字段

_l10n_es_edi_facturae_prepare_inv_line() 会把每一行转换成 XML 所需结构,包括:

  • ItemDescription
  • Quantity
  • UnitOfMeasure
  • GrossAmount
  • TotalCost
  • DiscountsAndRebates
  • Charges
  • TaxesOutputs
  • TaxesWithheld

这说明 Facturae 不是“把 Odoo 行对象 dump 成 XML”,而是要把会计行重新解释成协议规定的字段语义。

例如:

  • 折扣要拆成 DiscountsAndRebates
  • 负向折扣可能要写成 surcharge / charge;
  • 预提税与普通销项税要分开;
  • 金额还可能涉及欧元等值。

所以导出动作的真正工作量,不在模板标签,而在语义重建


导入链路为什么也这么复杂

模块并不只会导出。

它还会:

  • 通过 _get_import_file_type() 识别 Facturae 命名空间;
  • _unwrap_attachment() 把一个 XML 里嵌的多张 Invoice 拆开;
  • _import_invoice_facturae() 里按买卖方向找 partner;
  • 再读取 XML 值回填发票。

这说明官方把 Facturae 看成真正的双向交换格式,而不是单边输出模板。

这点非常重要,因为真实电子发票生态里,“能导出”通常只完成了一半。另一半是:

  • 你收到对方或平台回来的 Facturae 文件时,系统能不能重新理解它。

一句话理解 Odoo Facturae 模块的真正价值

如果只用一句话总结,我会这样说:

Odoo Facturae 模块的价值,不是把发票变成 XML,而是把会计发票变成一份可签名、可追溯、可交换、可回导的西班牙电子发票对象。

所以做西班牙电子发票项目时,真正要盯的不是“模板有没有生成”,而是这些更本质的边界:

  • 公司证书是否可靠;
  • partner 与行政中心信息是否齐;
  • 付款条款是否可结构化;
  • 红字票是否能追溯原票与原因码;
  • 导入时能否把同一格式重新读回来。

这也是为什么它比普通导出模块更像一条合规流水线。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。