PAYATHON 2026

如何通过 Odoo 19 JSON2 API 登记发票全额付款?

支付小周

结论

不要先创建 account.payment,然后把它的 ID 传给 account.payment.register/action_create_payments。

account.payment.register 是临时向导模型,正确的调用顺序是:

  1. 以发票 ID 作为 active_ids,创建 account.payment.register 向导。
  2. 从第一次调用的返回结果中取得向导 ID。
  3. 使用该向导 ID 调用 action_create_payments。

发票和付款并不是通过 account.payment.invoice_ids 关联的。付款登记向导会根据发票中尚未核销的应收或应付明细创建付款,并完成会计明细核销。

为什么当前调用会失败

第一次调用创建的是:

account.payment

第二次调用执行的却是:

account.payment.register/action_create_payments

这是两个不同的模型,其记录 ID 也没有关联。35 是一条 account.payment 记录的 ID,不是 account.payment.register 向导的 ID。

Odoo 会尝试从 account.payment.register 中读取 ID 为 35 的临时记录。如果该记录不存在、已经过期,或者没有待处理的 line_ids,就会触发以下错误:

You can't open the register payment wizard without at least one receivable/payable line

标准付款登记流程也不应通过下面的参数关联发票:

"invoice_ids": [16]

在 Odoo 的标准会计流程中,付款和发票最终通过应收、应付会计明细之间的核销关系建立关联。

正确的调用流程

第一步:创建付款登记向导

调用:

POST https://myhost/json/2/account.payment.register/create

请求体可以写成:

{
  "vals_list": {
    "journal_id": 6,
    "amount": 7.20
  },
  "context": {
    "active_model": "account.move",
    "active_ids": [16]
  }
}

如果接口要求 vals_list 必须是数组,则使用:

{
  "vals_list": [
    {
      "journal_id": 6,
      "amount": 7.20
    }
  ],
  "context": {
    "active_model": "account.move",
    "active_ids": [16]
  }
}

使用对象还是数组,应以当前 Odoo 19 实例对 create 方法的 JSON2 参数校验结果为准。无论使用哪种形式,都必须正确传入:

"active_model": "account.move",
"active_ids": [16]

创建向导时,Odoo 会根据这些上下文参数读取发票中尚未核销的应收或应付明细,并填充向导的 line_ids。

第一次调用返回的是付款登记向导 ID,例如:

35

或:

[35]

具体返回形式取决于调用参数和实例的序列化方式。

第二步:执行付款登记

使用上一步返回的向导 ID 调用:

POST https://myhost/json/2/account.payment.register/action_create_payments

请求体:

{
  "ids": [35],
  "context": {
    "active_model": "account.move",
    "active_ids": [16]
  }
}

这里的 35 必须来自:

account.payment.register/create

不能来自:

account.payment/create

action_create_payments 会执行以下操作:

  • 创建实际的 account.payment;
  • 过账付款;
  • 生成付款对应的会计分录;
  • 核销付款和发票的应收或应付明细;
  • 更新发票的付款状态及剩余未付金额。

全额付款时的金额处理

如果要支付发票的全部未付金额,可以不传 amount,由向导根据发票的 amount_residual 计算默认值:

{
  "vals_list": {
    "journal_id": 6
  },
  "context": {
    "active_model": "account.move",
    "active_ids": [16]
  }
}

与在客户端写死 7.20 相比,这种方式通常更稳妥,尤其适用于以下情况:

  • 发票已经部分付款;
  • 存在多币种;
  • 付款日期会影响汇率;
  • 发票发生过退款或部分核销;
  • 金额存在币种舍入差异。

如果需要指定付款日期,可以在向导中传入:

{
  "vals_list": {
    "journal_id": 6,
    "payment_date": "2026-08-31"
  },
  "context": {
    "active_model": "account.move",
    "active_ids": [16]
  }
}

日期应填写实际付款日期。

显式传递应收或应付明细

通常只需传入 active_model 和 active_ids。如果当前实例中的自定义模块导致向导无法根据上下文完成初始化,可以先查询发票中尚未核销的应收或应付明细,再显式填写 line_ids。

例如调用:

POST https://myhost/json/2/account.move.line/search

请求体:

{
  "domain": [
    ["move_id", "=", 16],
    [
      "account_id.account_type",
      "in",
      ["asset_receivable", "liability_payable"]
    ],
    ["reconciled", "=", false]
  ]
}

假设返回的明细 ID 为:

[101]

创建向导时,可以使用 x2many 命令:

{
  "vals_list": {
    "journal_id": 6,
    "amount": 7.20,
    "line_ids": [
      [6, 0, [101]]
    ]
  },
  "context": {
    "active_model": "account.move",
    "active_ids": [16]
  }
}

不过,最好还是让向导通过 active_ids 自动获取明细,因为它还会检查币种、合作伙伴、付款方向和批次等信息。

调用前需要确认

发票必须满足以下付款登记条件:

  • state 为 posted;
  • amount_residual 大于零;
  • 存在尚未核销的应收或应付明细;
  • 收款日记账 journal_id = 6 可用于当前公司和付款类型;
  • 发票与日记账所属公司兼容;
  • API 用户具有创建和过账付款,以及核销会计明细的权限。

客户发票通常包含 asset_receivable 明细,供应商账单通常包含 liability_payable 明细。因此,不能只根据是否存在 payable lines 来判断客户发票是否满足条件。

不建议直接创建 account.payment

直接调用:

account.payment/create

只会创建付款记录,不会自动完成付款与发票的核销。即使随后调用 account.payment/action_post,付款通常也只是完成过账,并未与目标发票匹配。

如果必须直接创建付款,还需要:

  1. 创建并过账 account.payment;
  2. 找到发票中尚未核销的应收或应付明细;
  3. 找到付款分录中同一账户对应的明细;
  4. 对这些 account.move.line 调用 reconcile。

对于普通的发票全额付款,使用 account.payment.register/create 和 account.payment.register/action_create_payments 更符合 Odoo 的标准业务流程。

备注:内容仅供参考。