先说结论
在 Odoo 里,采购退货不是“仓库退了就完了”。
一旦你把收进来的货又退回供应商,系统通常会同时重算三层东西:
qty_received:这条采购行到底还算收了多少qty_to_invoice:现在还应该向供应商确认多少账单,或者反过来要不要冲回去- 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_invoiced,in_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
评论区