先说结论
如果你已经理解了 route 决定方向、rule 决定动作,那接下来最容易把人绕进去的,就是 procure_method。
最实用的理解是:
procure_method决定这一步需求是优先吃当前来源位置的库存,还是继续把需求往上游传。
三种模式可以先粗暴记成:
make_to_stock:先当成“从现货里满足”make_to_order:不要先吃现货,直接继续往上游触发规则mts_else_mto:能吃现货先吃,不够的那部分再继续往上游传
所以它不是 route 的同义词,也不是采购/制造的别名。
它更像:
当前这一步供应动作采用什么供给策略。
这就是为什么同一条 route、同一条 rule,换个 procure_method,后面的 move 状态、procurement 传播、补货结果都可能变掉。
为什么很多人会把它和 route 混在一起
因为 UI 上更显眼的通常是 route。
比如你更容易先看到:
- Buy
- Manufacture
- Replenish on Order
- Dropship
而 procure_method 往往藏在 rule 细节里,不像路线那样一眼可见。
于是就容易产生一个误解:
- 只要路线选好了,系统自然知道该怎么补
其实不够。
因为 route 解决的是:
- 这条需求允许走哪种履约方向
而 procure_method 解决的是:
- 在当前这一步,来源位置是优先直接供货,还是继续往更上游再找供应
所以前者更像“选路”,后者更像“这一步怎么供”。
先看 Odoo 官方源码对三种模式的原始定义
在 addons/stock/models/stock_rule.py 里,stock.rule.procure_method 的定义就是:
make_to_stock→Take From Stockmake_to_order→Trigger Another Rulemts_else_mto→Take From Stock, if unavailable, Trigger Another Rule
这个定义其实已经很直白了。
make_to_stock
官方语义就是:
从当前来源位置的可用库存里拿。
make_to_order
官方语义就是:
不看当前来源位置有没有货,直接继续触发另一条规则,把货补到这里来。
mts_else_mto
官方语义就是:
能从当前来源位置拿就先拿,不够的缺口再继续触发下一层规则。
你会发现,三者真正的分界不是“是不是采购”或者“是不是制造”,而是:
- 当前来源位置的库存要不要优先参与这一步 fulfilment
为什么这三个选项本质上是在回答同一个问题
你可以把它们都理解成在回答这句:
“下游需要的货,当前来源位置要怎么供?”
三个答案分别是:
答案 1:make_to_stock
先直接从当前来源位置供。
答案 2:make_to_order
当前来源位置先别谈现货,直接让更上游去准备。
答案 3:mts_else_mto
当前来源位置能供多少先供多少,不足的那部分再往上游追。
一旦这样理解,你就不会再把它们误当成“采购模式”“制造模式”或者“路线名称的另一个写法”。
make_to_stock 到底在做什么
make_to_stock 最像的业务语义是:
当前来源位置被当成这一步的直接供货点。
也就是说,系统在这一步不会优先再往上游派生新的 procurement,而是先把 move 当成“从这里出货”的动作。
这不等于“仓里一定真的有足够库存”
这里很多人会误解。
make_to_stock 的意思不是:
- 系统一定验证这里货够
- 不够就立刻自动处理所有上游问题
它更准确地表示:
- 这一步 move 的供给语义是 from stock
后面能不能立刻 assign、会不会 waiting、是否触发其他补货机制,还要看 reservation、scheduler、orderpoint、可用量等其他机制。
所以 make_to_stock 不是“保证现货足够”,而是“先按库存供给语义来处理”。
make_to_order 到底在做什么
make_to_order 的核心不是“采购”,也不是“制造”。
它真正表达的是:
这一步不要先假定来源位置直接供货,而是继续创建 procurement,让系统再往上游找 rule。
这点在 stock.move._action_confirm() 里体现得非常明显。
源码里,如果 move 是 make_to_order:
- move 会进入
waiting - 同时会加入
move_create_proc - 然后系统调用
stock.rule.run(procurement_requests)
翻成人话就是:
这张 move 自己先别当最终供货者,它在确认时会继续把“我要货”这件事往上游再发一次。
这就是为什么 MTO 看起来像“会自己长链条”。
其实不是魔法,而是:
- move confirm 时又触发了一层 procurement
- 新 procurement 再去命中新一条 rule
- 新 rule 再决定是 pull / buy / manufacture / 其他动作
所以 make_to_order 真正强调的是:
- 需求继续传播
而不是某一种具体业务单据类型。
mts_else_mto 为什么最容易被误解
因为它是三者里最像真实业务、也最不像“一刀切”的那个。
它表达的是:
当前来源位置的库存不是完全不看,也不是绝对优先;系统会先吃可用量,再只为缺口继续派生 procurement。
这比纯 MTS 和纯 MTO 都更接近很多企业的真实需求。
例如:
- 仓里有 6 个
- 客户要 10 个
那业务上很多人想要的其实是:
- 先把 6 个现货吃掉
- 剩下 4 个再去补
而不是:
- 要么完全从库存拿
- 要么 10 个全部重新补
mts_else_mto 正是干这个的。
源码里 mts_else_mto 是怎么落地的
这一段非常关键。
很多人以为 mts_else_mto 会在一开始就生成一种“半库存、半补货”的神奇 move。
源码不是这么做的。
第一步:在 _run_pull() 里先把 move 当成 make_to_stock
在 stock_rule._run_pull() 里,Odoo 会先做一个转换:
- 如果 rule 的
procure_method == 'mts_else_mto' - 那生成 move values 时,先把 move 的
procure_method写成make_to_stock
这一步很有意思。
它意味着:
Odoo 先把这一步 move 落成“库存供给语义”的 move。
也就是说,落 move 这件事本身不会直接写成一半 MTO。
第二步:在 stock.move._action_confirm() 里单独把它识别出来
源码里又有一段专门逻辑:
- 如果 move 的
rule_id.procure_method == 'mts_else_mto' - 那这个 move 既会进入
confirmed - 也会加入
move_create_proc
这说明 Odoo 的思路不是“二选一”,而是:
- move 先按库存方向成立
- 同时再根据缺口继续创建 procurement
第三步:真正继续往上游补多少,靠 _prepare_procurement_qty() 算
这一步最值钱。
源码里会按 location + product 读取 free_qty,然后只为缺口部分生成 procurement 数量。
也就是说,系统不是傻傻地把整张需求再重新补一次,而是大致按这个逻辑:
缺口 = max(需求量 - 可用量, 0)
然后只为这个缺口继续触发上游 rule。
这就是 mts_else_mto 最关键的本质:
当前层先保留库存 fulfilment 语义,但补货传播只针对“不够的那部分”。
为什么 mts_else_mto 看起来总让人觉得“系统行为怪怪的”
因为它天然就是混合模式。
混合模式最容易带来三种错觉。
错觉 1:为什么 move 已经 confirmed 了,系统还在继续补货
因为这是它本来就该干的事。
- move 作为库存供给动作已经成立
- 但缺口还要继续往上游追
错觉 2:为什么有些数量像吃了库存,有些数量又像重新采购/制造了
因为这正是 mts_else_mto 的目标。
- 可用量部分走本地库存
- 缺口部分走上游 replenishment
错觉 3:为什么我看单据时总觉得来源不统一
因为它本来就是“同一步需求拆成两个供给来源”的策略。
如果你脑子里只有“全库存”或“全补货”两种模型,就很难读懂它。
三者真正的差异,不在名字,而在 move confirm 时怎么传播需求
如果你只用业务术语理解,会觉得它们像配置项。
如果你看源码,会发现真正的差异点在:
- move
_action_confirm()时,系统要不要继续创建 procurement,以及创建多少。
你可以这样记:
make_to_stock
- 当前 move 主要按库存供给处理
- 不会因为这个 procure_method 自己继续派生 procurement
make_to_order
- 当前 move 会进入 waiting
- 会继续派生 procurement
- 需求会被完整继续往上游传播
mts_else_mto
- 当前 move 先按库存方向存在
- 同时会继续派生 procurement
- 但只为缺口数量继续传播
这才是三者最核心的差异。
用一个最简单的数量例子讲透
假设下游要 10 个,当前来源位置可用量是 6 个。
如果是 make_to_stock
系统更偏向:
- 这 10 个需求先按“从这里供货”理解
- 至于能不能马上全保留 / assign / 后续怎么补,是别的机制的问题
如果是 make_to_order
系统更偏向:
- 这 10 个不要先假定来源位置自己供
- 直接把 10 个需求往上游继续发
如果是 mts_else_mto
系统更偏向:
- 当前层先保留 from stock 语义
- 只把缺的 4 个往上游继续发
所以业务上最常见的感觉会是:
- MTS:像本仓负责
- MTO:像上游负责
- MTSO:像本仓先负责一部分,剩下的再交给上游
这样就直观多了。
为什么它会直接影响采购、制造和调拨结果
因为采购、制造、内部调拨这些“动作类型”通常是 rule action 决定的;但需求会不会传到能命中这些 action 的上游规则那里,又取决于 procure_method。
换句话说:
- route / rule action 决定“上游如果被触发,会做什么”
- procure_method 决定“要不要把需求继续传到那个上游去”
所以很多人说:
- 为什么同样是买这条 route,有时直接动本仓库存,有时又去采购?
本质上常常不是 route 名字的问题,而是:
- 当前节点是 MTS 还是 MTO / MTSO
实战里最容易踩的 7 个坑
1. 把 MTO 理解成“采购模式”
错。它只是“继续触发上游规则”,上游可以是采购,也可以是制造,也可以是另一次 pull。
2. 把 MTS 理解成“库存一定够”
错。它只是当前步骤采用库存供给语义,不等于系统保证足量现货。
3. 低估 mts_else_mto 的复杂度
它最接近真实业务,但也最容易造成“为什么一部分这样、一部分那样”的错觉。
4. 只看 rule action,不看 procure_method
你会解释不了为什么同一 action 有时不继续补货、有时继续补。
5. 只看 move 创建,不看 move confirm
三者真正分叉常发生在 _action_confirm()。
6. 调试时不看 _prepare_procurement_qty()
尤其是 mts_else_mto,缺口数量到底怎么算,这里才是关键。
7. 以为 route、rule、procure_method 三者是重复字段
不是。它们分工完全不同:
- route:路径选择范围
- rule action:这一步做什么动作
- procure_method:当前来源层怎么供
最实用的调试顺序
如果你想快速看懂一条库存链为什么这样跑,我建议按这个顺序:
- 先看命中的 rule 是哪条
- 再看它的 action 是什么
- 再看它的
procure_method是什么 - 再看 move confirm 后有没有继续派生 procurement
- 如果是
mts_else_mto,重点看缺口数量是怎么算出来的
这比一上来只盯采购单、制造单、拣货单更快抓根因。
一句话记忆法
把三者记成一句话:
make_to_stock是“这一步先按库存供”,make_to_order是“这一步继续把需求往上游传”,mts_else_mto是“能从库存供的先供,不够的那部分再往上游传”。
理解这一句之后,你再看 Odoo 的库存、补货、采购、制造链路,就不会只停留在“路线名字像懂了”,而会真正看懂需求是怎么在每一层被供给、被传播、被拆分的。
DISCUSSION
评论区