很多人会配 Odoo route,也知道 push rule 跟 pull rule 不一样,但一到现场排错还是会糊:
- 为什么这一步 done 了,下一步 move 才突然出现?
- 为什么有时系统不新建 move,只是把目的库位改了?
- 为什么某些下游依赖链被转接过去,有些却断掉?
这些问题如果只用“push 是到货驱动”一句话去解释,远远不够。真正要看懂,得进源码里的 _push_apply() 和 stock.rule._run_push()。
push rule 的根问题不是“补货”,而是“到站以后怎么继续走”
pull rule 的核心问题是:
下游有需求时,上游要怎么被拉出来?
push rule 的核心问题则是:
某个 move 已经把货送到一个位置后,接下来是否还应该立刻继续往后推进?
所以 push rule 的思维时点天然更靠后。
它不是围绕“需求缺口”组织的, 而是围绕“货到了某个目的地以后,是否触发下一跳”组织的。
这也是为什么很多人会产生一种主观感受:
Push rule 看起来总像“做完才开始工作”。
这个感觉并没有错。
_push_apply() 才是读懂 push 的真正入口
如果你只看 stock.rule._run_push(),容易把它误解成“只是一条创建 move 的工厂函数”。
真正有意思的,是 stock.move._push_apply() 做的事:
- 以当前 move 的
location_dest_id为落点去找匹配的 push rule; - 规则命中后,判断是 transparent 还是需要 manual operation;
- 必要时创建新 move;
- 处理
move_dest_ids的转接或断链; - 对新 move 做 confirm,让链路继续长出来。
也就是说,push 不只是“造下一张单”,它还会重排依赖关系。
为什么有时不新建 move,只是改目标库位
这就是很多人第一次读到 auto == 'transparent' 时会愣住的地方。
transparent push rule 的语义不是“新加一步人工作业”,而更像:
这段中间节点对业务来说不值得单独落成一张新 move,直接把当前 move 的目标改到下一跳即可。
源码里它会:
- 记住旧目标库位;
- 把当前 move 的
location_dest_id改成规则的目标; - 若已有 move line,还同步改 line 的目标库位;
- 如果目标发生变化,再递归看后面是否还有下一条 push。
所以 transparent 的重点不是“没规则”,而是规则生效方式是改写当前动作,而不是增殖下一张动作。
这对多步仓设计很关键。
因为并不是每个物流节点都值得单独变成一张拣货单。有些节点只需要保留路由语义,不需要保留独立作业单据。
manual operation push 才是大家最熟悉的“生成下一步 move”
如果 rule 不是 transparent,源码会走复制 move 的路径。
这里的关键不是“复制一张一样的 move”,而是:
- 新 move 的来源,改成当前 move 的目的地;
- 新 move 的目标,改成 push rule 指定的下一站;
- 新 move 会带自己的 picking type、warehouse、rule_id;
- 在某些场景下,还会重写
procure_method。
所以新 move 并不是旧 move 的镜像,而是“把当前落点变成下一跳起点”的链式延伸。
这正是 push rule 能表达库内连续搬运的核心。
为什么 move_dest_ids 会被重新接线
这是 push 调试里最容易被忽略、但最有价值的细节。
当一个 move 因 push 规则产生了新 move 后,源码不会简单让原 move 和所有旧 downstream 关系继续混在一起。
它会根据场景决定:
- 哪些后续依赖应该转接到新 move;
- 哪些关系应该与原 move 断开;
- 哪些 move 需要从 MTO 依赖里被 break 掉。
这说明 push rule 不只是追加流程,而是在维护因果图:
- 如果货已经不是“直接从当前 move 到最终依赖”,那依赖关系也该重新挂;
- 如果中间多了一跳,真正的供应者也应该变成这张新 move。
因此现场上你看到“明明是一条库内规则,怎么把下游链路也改了”的时候,不是系统多事,而是它在努力保持依赖图真实。
push rule 和 pull rule 的根本差异,不止触发时机
当然,触发时机是第一层:
- pull:需求先来;
- push:货先到站。
但更深的一层是:
- pull 更像“为满足需求,向上游要一笔供给”;
- push 更像“当前这笔供给到了这里,还必须继续往后走”。
这两种机制对依赖图的改写方式完全不同。
所以你不能用 pull 的眼镜去看 push。
尤其不要把 push 理解成“另一种 procurement”。它更像库存动作完成后的流向接力器。
为什么 return move 默认要避开 push 再次触发
源码里还有个非常实战的保护:
如果当前 move 是 return 相关,系统会尽量避免让 push rule 再把它重新推进一遍,防止链路重复生成。
这个保护很重要,因为退货本来就是一种“逆向修正动作”。 如果它再按正向 push 规则继续自动推,就很容易制造双倍链路或错误中转。
所以你在排退货 / 逆向物流时,看见 push 不像普通出入库那样触发,往往是有意的。
实战调试顺序
遇到“为什么 push 没生效 / 生效方式不对”时,建议按这个顺序看:
- 当前 move 的
location_dest_id到底是不是规则命中的落点; - 规则是 transparent 还是 manual operation;
- move 当前是不是 return 场景;
- 规则是否有
push_domain限制,导致匹配后又被排除; - 新 move 的 picking type、warehouse、procure_method 是否符合预期;
move_dest_ids有没有被重新转接到新 move;- 新 move confirm 后,是否又继续触发了下一层 push。
这套顺序比单纯去看“有没有新建拣货单”要有效得多。
最后的结论
Push rule 真正解决的问题不是“怎么补货”,而是:
一笔货到了这个节点后,系统要不要继续替你把下一跳组织出来。
因此它天然带着三层语义:
- 触发时点在 move 落点之后;
- 规则可能只是改写当前 move,也可能新建下一张 move;
- 依赖链并不会原封不动,而是要按新的物流现实重新接线。
理解了这三层,你再看 Odoo 的库内链式流转、跨区中转、透明节点和人工步骤,就不会再把 push 当成“晚一点执行的 pull”了。
DISCUSSION
评论区