先说结论
很多人会把 Facturae 想成“西班牙电子发票导出 XML”,但从 /home/ubuntu/odoo-temp/addons/l10n_es_edi_facturae/models/account_move.py 来看,真正复杂的不是导出动作本身,而是一张西班牙发票在变成可交换、可签名、可纠错的合规对象之前,需要先把业务上下文补齐到什么程度。
这条链至少包含:
- 公司有没有可用于 Facturae 的证书。
- 伙伴、国家、VAT、行政中心角色是否齐。
- 支付条款能否转换成 XML 里的 installment 结构。
- 红字 / 更正发票是否能追溯到被冲原票及原因码。
- 导出的 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 所需结构,包括:
ItemDescriptionQuantityUnitOfMeasureGrossAmountTotalCostDiscountsAndRebatesChargesTaxesOutputsTaxesWithheld
这说明 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
评论区