先说结论
Odoo 电商库存判断,并不是一句简单的“仓库里还有多少”。
website_sale_stock 真正关心的是三件事:
- 当前网站应该按哪个仓库看库存;
- 当前购物车里这个商品已经占了多少数量;
- 这个商品是否允许缺货继续下单。
所以前台看到的“还剩 3 件”“库存不足”“允许继续购买”,背后并不是同一条规则在换皮,而是:
free_qty、cart_qty、网站仓库和缺货策略共同决定的前台库存边界。
这就是为什么很多实施里“明明库存还有,前台却说不够”或者“列表页能买,结账页又被拦”,其实都不是 bug,而是不同层级的库存语义在起作用。
一、为什么 Odoo 网站库存先看 website warehouse
website_sale_stock 在 sale.order 上重写了 _compute_warehouse_id() 和 _get_shop_warehouse_id()。
核心思路很清楚:
- 网站订单优先使用网站自己的仓库;
- 如果网站没配置仓库,再回退默认逻辑。
这说明 Odoo 的网站库存不是“公司全局库存”的直接投影,而是:
- 以网站视角理解库存。
这在多仓、多站点场景特别关键。
因为业务真实问题不是:
- 系统里总共有多少货;
而是:
- 这个网站面向的履约仓,现在还能卖多少。
二、为什么前台库存不是只看 free_qty
很多人只盯着 product.free_qty,但源码里更重要的是:
_get_cart_and_free_qty(product)_get_cart_qty(product_id)_get_free_qty(product)
Odoo 会把两类数量一起算:
- 仓库维度的可用库存
free_qty; - 当前购物车里已经放进去的数量
cart_qty。
这点特别容易被忽略。
因为如果你只看 free_qty,一个用户可以在同一购物车里反复加购,前台每次都觉得“库存还够”,最后总数却超了。
Odoo 明确避免这个问题:
库存边界不是“单次加多少”,而是“加完之后整张 cart 一共会占多少”。
三、为什么 _verify_updated_quantity() 是真正的库存闸门
website_sale_stock/models/sale_order.py 里,最关键的方法就是 _verify_updated_quantity()。
当商品是可库存商品且 allow_out_of_stock_order=False 时,系统会:
- 取出当前 cart 里该商品数量;
- 取出对应网站仓库下的
free_qty; - 把数量统一换算到当前请求 UoM;
- 计算这次改动后的
total_cart_qty; - 如果超出,就返回允许的最大数量和 warning。
这条链路说明 Odoo 对库存限制的理解不是“超了就抛错结束”,而是更偏用户友好:
- 能留多少就留多少;
- 不能留时明确告诉你为什么;
- 必要时直接把 line 降量甚至移除。
这也是为什么前台有时不是报硬错误,而是自动把数量调回可接受范围。
四、为什么商品页提示和支付前校验不是同一层
website_sale_stock 同时改了:
- 商品组合信息返回值;
- 前台 JS 的 availability message;
- 支付前
_check_cart_is_ready_to_be_paid()。
这说明 Odoo 故意做了两层边界:
第一层:前台提示层
商品页、变体切换、数量输入框会根据:
free_qtycart_qtyshow_availabilityallow_out_of_stock_order
去渲染提醒、badge、最大可输数量。
第二层:支付阻断层
真正去付款前,还会再检查一遍各行 _check_availability(),如果仍有问题,就在 _check_cart_is_ready_to_be_paid() 抛出校验异常。
这很合理,因为前台提示层更多是用户体验;支付前校验层才是最后业务边界。
所以“页面能加购物车,但付款时被拦住”并不一定矛盾,而是:
- 前台提示是尽早提醒;
- 支付校验是最后兜底。
五、为什么“允许缺货下单”会彻底改变库存语义
allow_out_of_stock_order 是这套机制里最该被认真理解的开关。
一旦它为真:
- 很多限制逻辑会直接放松;
_is_sold_out()的语义会改变;- 前台库存文案也会偏向继续下单而不是强拦截。
这不是一个普通展示选项,而是商业策略开关。
它决定你的网站到底是:
- 库存严控型电商;
- 还是允许预售/延期履约型电商。
实施时最容易出错的地方,就是业务方口头上说“缺货也能接单”,但页面、支付、提醒、库存邮件却还按“现货严控”思维在跑。
六、为什么购物车里的库存感知比商品列表更重要
源码里不少逻辑都优先围绕 request.cart 做文章,而不是只盯商品卡片。
这非常合理。
因为网站库存真正危险的地方,不是用户看到某商品页面时,而是:
- 他已经把多个商品、多个数量、多个变体一起放入同一 cart 之后。
也就是说,电商库存管理最重要的不是“商品当前能否展示可买按钮”,而是:
- 这张购物车现在还能不能成立。
所以 Odoo 会反复回到 cart 维度去重算,而不是只在商品页做一次展示判断。
七、最容易误解的 5 件事
1. 以为网站库存就是公司总库存
不对。网站可以绑定自己的仓库视角。
2. 以为前台限制只看 free_qty
不对。源码还会扣掉当前 cart 已有数量。
3. 以为页面提示和支付校验是同一层逻辑
不对。一个偏体验,一个偏最终边界。
4. 以为“允许缺货下单”只是把按钮放开
不对。它会改变整套库存语义。
5. 以为库存问题一定是商品页问题
不对。真正关键的是整张购物车在当前仓库视角下是否还能成立。
八、实战排错顺序
电商库存提示不一致时,我会按这个顺序看:
- 网站绑定的是哪个仓库;
- 商品是否
allow_out_of_stock_order; - 当前 cart 里该商品已经有多少;
- UoM 换算后数量是否被 round down;
- 支付前
_check_cart_is_ready_to_be_paid()是否仍发现某行不可用。
这样查,通常比只在前台盯着“库存数字怎么显示”更快找到根因。
一句话记忆
Odoo 电商库存不是“仓里还有多少”的单点判断,而是“网站仓库下的
free_qty,减去当前 cart 已占数量,再结合缺货策略后形成的一整套可下单边界”。
DISCUSSION
评论区