采购退货

Odoo 采购退货别只看退货单:qty_received、qty_to_invoice 与 Vendor Refund 是怎么一起回滚的

采购退货不是做一张 return picking 就结束了。本文把 Odoo 里采购退货、已收数量、待开票数量和供应商退款之间的真实联动讲清楚。

Odoo 开发 会计 库存 采购
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

在 Odoo 里,采购退货不是“仓库退了就完了”

一旦你把收进来的货又退回供应商,系统通常会同时重算三层东西:

  1. qty_received:这条采购行到底还算收了多少
  2. qty_to_invoice:现在还应该向供应商确认多少账单,或者反过来要不要冲回去
  3. Vendor Refund:如果之前已经按更多数量入了账,现在就可能需要供应商退款单

所以采购退货真正麻烦的地方,不在退货动作本身,而在:

退货会把“收货事实”反向改写,而 Odoo 后续的开票与状态,正是建立在这个收货事实上的。


这篇为什么不等于“退货 / scrap / 调整边界”

站里已经有文章讲过:

  • scrap 是报废
  • return 是回退原业务流
  • inventory adjustment 是盘点修正

但采购退货再往深一层,会碰到一个更实战的问题:

  • 为什么退货后 PO 的已收数量变少了?
  • 为什么有时 invoice_status 重新变成 to invoice
  • 为什么有时竟然出现负的 qty_to_invoice
  • 什么时候应该开 in_refund,什么时候只是做库存退货?

这已经不是“动作类型分类题”,而是 采购、库存、会计三层联动题


源码里谁在决定“退货后还算不算已收”

关键逻辑在 purchase_stock/models/purchase_order_line.py_prepare_qty_received()

这段代码不是简单地把 done 的 move 全部相加,而是按 move 类型分情况处理:

  • 正常采购入库 move:加到 qty_received
  • move._is_purchase_return():说明这是采购退货 move
  • 如果它是有效的退供应商动作,会把数量 减回去
  • 某些 dropship return 边界还会专门跳过,避免重复统计

这说明:

采购行上的 qty_received 本质上不是“历史上曾经收过多少”,而是“截至当前,净收货还剩多少”。

很多人脑子里记的是累计收货,Odoo 算的是净结果,这就是第一层认知差。


为什么退货会把 qty_to_invoice 变成负数

关键在 purchase/models/purchase_order_line.py_compute_qty_invoiced()

如果产品走 按收货数量开票

qty_to_invoice = qty_received - qty_invoiced

这时如果你已经:

  • 收货 10
  • 开票 10

然后又退货 5,系统会把净收货改成 5。于是:

qty_to_invoice = 5 - 10 = -5

这个负数不是 bug,反而很有价值。它表达的是:

你已经按比当前净收货更高的数量入账了,现在不是“还能再开多少”,而是“理论上要冲回多少”。

Odoo 官方测试 test_vendor_bill_delivered_return 就明确验证了这条链:

  • 先收 10、开票 10
  • 再把 qty_received 改成 5
  • 结果 qty_to_invoice 变成 -5
  • 接着再创建账单动作,最终把已开票数量修正回 5

这就是采购退货和 Vendor Refund 的会计交叉点。


为什么“退货”和“退款”不是一回事

这点最容易混。

库存退货做的是什么

库存退货做的是:

  • 把货 physically 退回供应商
  • 反向影响采购行的净收货数量

Vendor Refund 做的是什么

Vendor Refund 做的是:

  • 把你之前多确认的应付账款冲回去
  • 通过 in_refund 影响 qty_invoiced

_prepare_qty_invoiced() 里,in_invoice 会增加 qty_invoicedin_refund 会减掉 qty_invoiced

所以从源码语义看:

  • 退货修正的是收货事实
  • 退款修正的是开票事实

两者经常一起发生,但它们不是同一个动作。


to_refund 为什么很关键

官方测试里做采购退货时,会在 return wizard 的 product_return_moves 上把 to_refund 勾上。

这个字段重要的原因不是“界面好看”,而是它在退货语义里告诉系统:

  • 这次退货不是单纯物流逆向
  • 它应该带着“后续需要账务冲回”的业务意图

也正因为这样,退货数量才不只是影响库存,还会自然把人带到 Vendor Refund 的后续动作上。


实战里最容易踩的 4 个坑

1. 只做退货,不做退款

库存对了,应付还挂着,账就歪了。

2. 把负的 qty_to_invoice 当系统异常

它常常是在提醒你:之前账开多了。

3. 以为 qty_received 记录的是历史累计值

在采购退货场景里,它更像净值。

4. 没区分“按订购开票”和“按收货开票”

如果产品走 ordered quantities,退货对开票状态的影响就和 received quantities 很不一样。


一句话记忆法

把它记成一句话:

采购退货会先改写采购行的净收货事实;如果账单已经按更多数量确认,Odoo 接着就会把问题暴露在负的 qty_to_invoice 和 Vendor Refund 需求上。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。