如何在 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 对应的金额,可以直接登记部分付款:
- 打开发票。
- 点击“登记付款”。
- 将付款金额改为 product A 实际应付的金额。
- 确认付款。
- 发票会保留剩余未付金额,付款状态通常显示为部分付款。
付款时应填写实际需要结算的金额。如果 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 创建另一张发票。
对已过账发票进行拆分
不宜直接修改已过账发票的会计行。通常需要按照当地会计和税务要求进行处理:
- 为原发票创建贷项通知单,或执行合法的冲销流程。
- 分别为 product A 和 product B 创建新发票。
- 将付款核销到 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 已结清,则应拆成独立发票。只有业务上必须保留单张发票时,才适合通过自定义分配表记录产品级付款归属。
备注:内容仅供参考。
