PAYATHON 2026

如何在 Odoo 中按 invoice 的 product 分别付款

支付小周

结论

Odoo 标准功能允许对发票进行部分付款,但不能把付款直接分配给某个 invoice line 或 product。

你可以支付与 product A 对应的金额,使发票进入“部分付款”状态。不过在会计记录中,这笔款项只会冲销发票的应收账款,系统不会将其标记为“product A 已付款、product B 未付款”。如果业务上必须明确区分两个产品的付款情况,最稳妥的处理方式是分别开票。

为什么不能直接按产品付款

客户发票过账后,Odoo 通常会生成类似以下的会计分录:

借:应收账款                     300
贷:产品 A 销售收入              100
贷:产品 B 销售收入              200

收到 100 元后,付款分录通常为:

借:银行存款或未核销收款账户      100
贷:应收账款                     100

之后,这 100 元会与发票的应收账款行进行部分核销。

Odoo 核销的是 account.move.line 中的应收或应付行,不是产品收入行。产品对应的收入、税额和分析会计记录在发票过账时已经生成,不会等到收到付款后再生成。

即使付款金额正好等于 product A 的含税金额,标准会计处理仍然只能说明:

  • 发票已支付一部分;
  • 应收余额已经减少;
  • 标准核销关系无法证明这部分付款属于 product A。

使用标准部分付款

如果只需要先收取 product A 对应的金额,可以直接登记部分付款:

  1. 打开发票。
  2. 点击“登记付款”。
  3. 将付款金额改为 product A 实际应付的金额。
  4. 确认付款。
  5. 发票会保留剩余未付金额,付款状态通常显示为部分付款。

付款时应填写实际需要结算的金额。如果 product A 涉及税费,一般应参考发票行的含税金额,而不是仅使用未税小计:

product_a_amount = sum(
    line.price_total
    for line in invoice.invoice_line_ids
    if line.product_id == product_a
)

其中,price_total 通常是发票行的含税金额,price_subtotal 通常是未税金额。实际计算时还要处理折扣、舍入、币种和全局税额调整。

通过 Odoo ORM 登记部分付款

以下服务器端代码使用 account.payment.register 登记指定金额的付款:

payment_register = self.env["account.payment.register"].with_context(
    active_model="account.move",
    active_ids=invoice.ids,
).create({
    "amount": product_a_amount,
    "payment_date": fields.Date.context_today(self),
    "journal_id": bank_journal.id,
})

payment_register.action_create_payments()

这段代码登记的是一笔金额等于 product A 的部分付款,并不会在标准数据模型中建立付款与产品行之间的对应关系。

account.payment.register 是付款登记向导。不同 Odoo 版本所需的必填字段、付款方式、上下文参数和批量付款逻辑可能不同,使用前应检查当前版本中的模型字段及 action_create_payments() 实现。

需要明确按产品结算时的处理方式

分别开具发票

最清晰的方案是分别开票:

Invoice 1:product A
Invoice 2:product B

支付 Invoice 1 后,付款、应收核销、收入分录和发票状态之间的关系都很明确,也方便后续催收、退款、对账和审计。

如果原发票尚未过账,可以删除 product B 的发票行,再为 product B 创建另一张发票。

对已过账发票进行拆分

不宜直接修改已过账发票的会计行。通常需要按照当地会计和税务要求进行处理:

  1. 为原发票创建贷项通知单,或执行合法的冲销流程。
  2. 分别为 product A 和 product B 创建新发票。
  3. 将付款核销到 product A 的发票。

能否取消、冲销或重新开票,取决于 Odoo 版本、本地化模块、锁定日期和当地税务规则。

增加自定义付款分配记录

如果必须保留一张发票,同时又要在业务报表中显示每个产品的已付金额,可以增加独立的分配模型,例如:

class PaymentProductAllocation(models.Model):
    _name = "payment.product.allocation"
    _description = "Payment Product Allocation"

    payment_id = fields.Many2one(
        "account.payment",
        required=True,
        ondelete="cascade",
    )
    invoice_line_id = fields.Many2one(
        "account.move.line",
        required=True,
        domain=[("display_type", "=", "product")],
    )
    amount = fields.Monetary(required=True)
    currency_id = fields.Many2one(
        "res.currency",
        required=True,
    )

这种方式可以用于统计业务层面的付款归属,但不应随意改动 Odoo 原有的应收核销机制。自定义实现还需验证以下事项:

  • 分配总额不能超过实际付款金额;
  • 单行累计分配不能超过约定的可付金额;
  • 退款、撤销付款和汇兑差额需要同步处理;
  • 税额、折扣和舍入应使用一致的分摊规则;
  • 多币种付款需要区分付款币种、发票币种和公司本位币。

注意事项

分析会计可以跟踪产品、项目或成本中心的收入与成本,但不能代替应收账款核销,也不能自动证明某笔客户付款属于某个产品。

如果只是分期收款,使用标准部分付款即可。如果合同、对账单或审计要求明确显示 product A 已结清,则应拆成独立发票。只有业务上必须保留单张发票时,才适合通过自定义分配表记录产品级付款归属。

备注:内容仅供参考。