订阅税务最危险的地方在于“客户还没到下一期,但门户上已经看到价格”;如果税额是外部服务计算,就必须保证预览和正式开票口径一致。
核心链路
models/sale_subscription.py的_create_recurring_invoice()在父类生成发票后,立即对这些未验证单据调用_get_and_set_external_taxes_on_eligible_records()。企业版认为 recurring 发票在验证前就应该带上正确税额。_do_payment()又在支付前再次触发税计算,避免客户支付时税额还是旧值。对订阅来说,开票与支付并不是一瞬间的同一事件。_get_external_tax_service_params()如果当前订单是 subscription,会把document_date设成next_invoice_date。这很关键:订阅税额通常应基于下一期开票日期,而不是今天浏览门户的日期。_get_line_data_for_external_taxes()更是专门过滤 confirmed subscription 上真正order._get_invoiceable_lines()的行;尚不可开票的订阅行不会被提前送去算税。- 控制器
controllers/portal.py的subscription()在渲染 portal 页面前,也会对订单调用_get_and_set_external_taxes_on_eligible_records()。所以客户在门户里看到的订阅金额,理论上应与正式开票保持同一税服务口径。
关键源码位置
/home/ubuntu/odoo-temp/enterprise/sale_subscription_external_tax/models/sale_subscription.py/home/ubuntu/odoo-temp/enterprise/sale_subscription_external_tax/controllers/portal.py
容易误解的地方
- 误区一:外部税只在发票 validate 时才需要。企业版在 recurring invoice 生成和支付前都可能重算。
- 误区二:门户预览展示的是静态价格。这里会在展示前主动刷新外部税。
- 误区三:所有订阅行都应送税服务。源码只保留真正可开票的行。
实战注意事项
- 跨州/跨国订阅业务里,先定义税服务的
document_date基准,否则同一订阅可能今天看和出账日看税额不同。 - 若客户投诉 portal 金额与发票不一致,优先检查门户控制器是否正确跑了外部税刷新。
- 做税务接口压测时,不要只测批量出账,也要测高并发 portal 浏览。
结语
企业版这些代码共同说明一件事:真正可上线的业务流程,靠的不是“页面上看起来能点通”,而是权限、状态、时机、对账口径和跨模块回写都被收紧。理解这些边界,实施和二开时就不容易走进“功能演示能跑、真实业务一用就散”的坑。
DISCUSSION
评论区