odoo 8 中 Invoice 注册全额付款后未变为 paid
结论
在 Odoo 8 中,判断发票是否已付款,看的不是有没有创建一笔等额付款,而是发票对应的应收账款或应付账款分录,是否已经与付款分录完成核销(reconciliation)。
即使付款金额与发票总额完全一致,只要这笔付款没有正确分配给发票,或者相关会计分录尚未核销,发票就不会变为 paid。处理时应检查并完成核销,不要直接修改发票的 state 字段。
为什么等额付款仍未变为 paid
发票验证后,Odoo 会生成会计凭证。客户发票通常包含:
- 收入科目分录;
- 税务分录;
- 客户应收账款分录。
登记付款后,系统还会生成银行、现金或其他付款日记账分录。付款中的应收账款分录与发票中的应收账款分录核销后,发票的未付余额才会降为 0,状态也才会变为 paid。
常见原因有:
- 已创建付款,但没有将其分配给对应发票;
- 付款使用了错误的客户;
- 付款分录与发票分录使用的应收账款科目不同;
- 付款被记入其他科目,没有产生可与发票核销的分录;
- 存在汇率、税额或舍入差异,残余金额没有归零;
- 自定义模块只创建了会计凭证,没有执行核销;
- 会计凭证仍是草稿状态;
- 自定义代码直接修改付款记录,绕过了 Odoo 8 的付款和核销流程。
先检查发票的未付余额
打开发票,检查以下内容:
- 发票是否已经 Validate;
Residual Amount是否为0;- 该发票的付款记录中是否显示了这笔付款;
- 发票与付款是否属于同一客户、公司和币种;
- 两边使用的应收账款科目是否一致。
如果付款已经登记,但 Residual Amount 仍等于发票金额,通常表示付款没有核销到这张发票。
如果只剩下很小的余额,例如 0.01,则要检查币种精度、汇率和舍入差异。
通过界面重新登记付款
可以先取消或撤销未正确分配的付款,再从发票页面重新登记:
- 打开发票,确认状态为
Open。 - 点击
Register Payment。 - 选择正确的付款日记账。
- 确认付款客户与发票客户一致。
- 在付款分配区域确认金额已经分配给当前发票。
- 如果存在付款差额,选择保留未核销余额,或使用差额科目冲销。
- 验证付款。
- 返回发票,确认未付余额是否为
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 的核销逻辑,并查看服务器日志中是否有工作流或约束异常。
备注:内容仅供参考。
