PAYATHON 2026

odoo 8 中 Invoice 注册全额付款后未变为 paid

支付老李

结论

在 Odoo 8 中,判断发票是否已付款,看的不是有没有创建一笔等额付款,而是发票对应的应收账款或应付账款分录,是否已经与付款分录完成核销(reconciliation)。

即使付款金额与发票总额完全一致,只要这笔付款没有正确分配给发票,或者相关会计分录尚未核销,发票就不会变为 paid。处理时应检查并完成核销,不要直接修改发票的 state 字段。

为什么等额付款仍未变为 paid

发票验证后,Odoo 会生成会计凭证。客户发票通常包含:

  • 收入科目分录;
  • 税务分录;
  • 客户应收账款分录。

登记付款后,系统还会生成银行、现金或其他付款日记账分录。付款中的应收账款分录与发票中的应收账款分录核销后,发票的未付余额才会降为 0,状态也才会变为 paid。

常见原因有:

  • 已创建付款,但没有将其分配给对应发票;
  • 付款使用了错误的客户;
  • 付款分录与发票分录使用的应收账款科目不同;
  • 付款被记入其他科目,没有产生可与发票核销的分录;
  • 存在汇率、税额或舍入差异,残余金额没有归零;
  • 自定义模块只创建了会计凭证,没有执行核销;
  • 会计凭证仍是草稿状态;
  • 自定义代码直接修改付款记录,绕过了 Odoo 8 的付款和核销流程。

先检查发票的未付余额

打开发票,检查以下内容:

  • 发票是否已经 Validate;
  • Residual Amount 是否为 0;
  • 该发票的付款记录中是否显示了这笔付款;
  • 发票与付款是否属于同一客户、公司和币种;
  • 两边使用的应收账款科目是否一致。

如果付款已经登记,但 Residual Amount 仍等于发票金额,通常表示付款没有核销到这张发票。

如果只剩下很小的余额,例如 0.01,则要检查币种精度、汇率和舍入差异。

通过界面重新登记付款

可以先取消或撤销未正确分配的付款,再从发票页面重新登记:

  1. 打开发票,确认状态为 Open。
  2. 点击 Register Payment。
  3. 选择正确的付款日记账。
  4. 确认付款客户与发票客户一致。
  5. 在付款分配区域确认金额已经分配给当前发票。
  6. 如果存在付款差额,选择保留未核销余额,或使用差额科目冲销。
  7. 验证付款。
  8. 返回发票,确认未付余额是否为 0,状态是否变为 Paid。

Odoo 8 通常通过 account.voucher 登记付款。如果系统中没有相应功能,或者付款界面的行为异常,应确认是否已安装 account_voucher 模块,以及相关功能是否被自定义模块覆盖。

手动检查并执行核销

如果付款已经生成,却没有自动匹配,可以进入会计分录或手工核销界面,找到下面两条分录:

  • 发票产生的客户应收借方分录;
  • 付款产生的客户应收贷方分录。

核对这些条件:

  • partner_id 相同;
  • account_id 相同;
  • company_id 相同;
  • 两条分录均已过账;
  • 借贷方向相反;
  • 未核销余额能够相互抵销。

确认无误后,对两条分录执行 Full Reconcile。核销完成后,再查看发票的残余金额和状态。

不要直接将银行科目分录与发票的应收账款分录核销。需要参与核销的是付款凭证中使用应收账款科目的那一行。

自定义代码中的处理方式

如果付款由自定义模块创建,不能只创建 account.move 或 account.voucher。代码还需要将付款过账,并核销付款与发票各自的应收账款分录。

下面的 Odoo 8 新 API 示例用于说明核销逻辑。字段名称可能会因已安装的模块或定制内容而不同,执行前应先在测试数据库中确认:

invoice = env['account.invoice'].browse(invoice_id)
payment_line = env['account.move.line'].browse(payment_move_line_id)

invoice_lines = invoice.move_id.line_id.filtered(
    lambda line:
        line.account_id.id == invoice.account_id.id
        and line.partner_id.id == invoice.partner_id.id
        and not line.reconcile_id
)

lines_to_reconcile = invoice_lines | payment_line

if any(line.state != 'valid' for line in lines_to_reconcile):
    raise Warning('All journal items must be posted before reconciliation.')

if len(lines_to_reconcile.mapped('account_id')) != 1:
    raise Warning('Invoice and payment must use the same receivable account.')

if len(lines_to_reconcile.mapped('partner_id')) != 1:
    raise Warning('Invoice and payment must belong to the same partner.')

lines_to_reconcile.reconcile()

在部分 Odoo 8 环境中,account.move 使用 line_id 作为分录字段。经过移植或定制后,字段可能不同,应以当前环境的模型定义为准。

更稳妥的处理方式是通过标准付款流程创建并验证 account.voucher,由 Odoo 根据付款分配明细自动完成核销,避免自行拼装底层会计分录。

可用于排查的日志代码

可以临时输出发票应收分录的核销情况:

invoice = env['account.invoice'].browse(invoice_id)

for line in invoice.move_id.line_id:
    if line.account_id.id == invoice.account_id.id:
        _logger.info(
            'invoice=%s line=%s account=%s debit=%s credit=%s '
            'reconcile_id=%s reconcile_partial_id=%s',
            invoice.number,
            line.id,
            line.account_id.code,
            line.debit,
            line.credit,
            line.reconcile_id.id,
            line.reconcile_partial_id.id,
        )

_logger.info(
    'invoice=%s state=%s residual=%s',
    invoice.number,
    invoice.state,
    invoice.residual,
)

如果 residual 不为 0,就要继续检查付款分录和核销关系,不能只根据付款单金额判断发票是否已经结清。

不要直接修改发票状态

不要用下面的方式强制修改状态:

invoice.write({'state': 'paid'})

这样做不会建立真实的会计核销关系,可能造成:

  • 发票显示已付款,但应收账款仍未结清;
  • 客户余额和账龄分析不准确;
  • 后续取消、退款或对账失败;
  • 总账记录与发票状态不一致。

正确的处理顺序是:

验证发票
→ 创建并过账付款
→ 核销应收/应付分录
→ 未付余额归零
→ 发票变为 paid

如果完成全额核销后,发票仍停留在 open,应检查自定义模块是否覆盖了 account.invoice、account.voucher 或 account.move.line 的核销逻辑,并查看服务器日志中是否有工作流或约束异常。

备注:内容仅供参考。